728x90

 

안녕하세요, 이번 포스팅은 Karpenter on EKS Fargate 배포에 대해서 포스팅을 올립니다.

CloudNet@의 테라폼 스터디 내용을 기반하고 있습니다.

 

오늘 사용할 Karpenter는 Kubernetes 클러스터 오토스케일러이고,

EKS Fargate는 관리형 서버리스 컨테이너 실행 서비스입니다.

 

먼저 git clone 후 하기 디렉토리로 이동합니다.

 

1. git clone

git clone https://github.com/aws-ia/terraform-aws-eks-blueprints
cd terraform-aws-eks-blueprints/patterns/karpenter

 

tree 명령어를 수행하면 다음과 같이 확인됩니다.

 

versions.tf는 다음과 같습니다.

 

2. versions.tf 확인

terraform {
  required_version = ">= 1.3"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.34"
    }
    helm = {
      source  = "hashicorp/helm"
      version = ">= 2.9"
    }
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = ">= 2.20"
    }
  }

  # ##  Used for end-to-end testing on project; update to suit your needs
  # backend "s3" {
  #   bucket = "terraform-ssp-github-actions-state"
  #   region = "us-west-2"
  #   key    = "e2e/karpenter/terraform.tfstate"
  # }
}

 

aws, helm, kubernetes 프로바이더의 버전이 명시된 것을 확인합니다.

테라폼도 1.3 이상이 요구됩니다.

 

3. main.tf 확인

main.tf입니다.

provider "aws" {
  region = local.region
}

# Required for public ECR where Karpenter artifacts are hosted
provider "aws" {
  region = "us-east-1"
  alias  = "virginia"
}

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]
  }
}

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]
    }
  }
}

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

data "aws_availability_zones" "available" {}

locals {
  name   = "t101-${basename(path.cwd)}"
  region = "ap-northeast-2"

  vpc_cidr = "10.10.0.0/16"
  azs      = slice(data.aws_availability_zones.available.names, 0, 3)

  tags = {
    Blueprint  = local.name
    GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints"
  }
}

################################################################################
# Cluster
################################################################################

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.11"

  cluster_name                   = local.name
  cluster_version                = "1.30"
  cluster_endpoint_public_access = true

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  # Fargate profiles use the cluster primary security group so these are not utilized
  create_cluster_security_group = false
  create_node_security_group    = false

  enable_cluster_creator_admin_permissions = true

  fargate_profiles = {
    karpenter = {
      selectors = [
        { namespace = "karpenter" }
      ]
    }
    kube_system = {
      name = "kube-system"
      selectors = [
        { namespace = "kube-system" }
      ]
    }
  }

  tags = merge(local.tags, {
    # NOTE - if creating multiple security groups with this module, only tag the
    # security group that Karpenter should utilize with the following tag
    # (i.e. - at most, only one security group should have this tag in your account)
    "karpenter.sh/discovery" = local.name
  })
}

################################################################################
# EKS Blueprints Addons
################################################################################

module "eks_blueprints_addons" {
  source  = "aws-ia/eks-blueprints-addons/aws"
  version = "~> 1.16"

  cluster_name      = module.eks.cluster_name
  cluster_endpoint  = module.eks.cluster_endpoint
  cluster_version   = module.eks.cluster_version
  oidc_provider_arn = module.eks.oidc_provider_arn

  # We want to wait for the Fargate profiles to be deployed first
  create_delay_dependencies = [for prof in module.eks.fargate_profiles : prof.fargate_profile_arn]

  eks_addons = {
    coredns = {
      configuration_values = jsonencode({
        computeType = "Fargate"
        # Ensure that the we fully utilize the minimum amount of resources that are supplied by
        # Fargate https://docs.aws.amazon.com/eks/latest/userguide/fargate-pod-configuration.html
        # Fargate adds 256 MB to each pod's memory reservation for the required Kubernetes
        # components (kubelet, kube-proxy, and containerd). Fargate rounds up to the following
        # compute configuration that most closely matches the sum of vCPU and memory requests in
        # order to ensure pods always have the resources that they need to run.
        resources = {
          limits = {
            cpu = "0.25"
            # We are targeting the smallest Task size of 512Mb, so we subtract 256Mb from the
            # request/limit to ensure we can fit within that task
            memory = "256M"
          }
          requests = {
            cpu = "0.25"
            # We are targeting the smallest Task size of 512Mb, so we subtract 256Mb from the
            # request/limit to ensure we can fit within that task
            memory = "256M"
          }
        }
      })
    }
    vpc-cni    = {}
    kube-proxy = {}
  }

  enable_karpenter = true

  karpenter = {
    repository_username = data.aws_ecrpublic_authorization_token.token.user_name
    repository_password = data.aws_ecrpublic_authorization_token.token.password
  }

  karpenter_node = {
    # Use static name so that it matches what is defined in `karpenter.yaml` example manifest
    iam_role_use_name_prefix = false
  }

  tags = local.tags
}

resource "aws_eks_access_entry" "karpenter_node_access_entry" {
  cluster_name      = module.eks.cluster_name
  principal_arn     = module.eks_blueprints_addons.karpenter.node_iam_role_arn
  kubernetes_groups = []
  type              = "EC2_LINUX"
}

################################################################################
# Supporting Resources
################################################################################

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = local.name
  cidr = local.vpc_cidr

  azs             = local.azs
  private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
  public_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)]

  enable_nat_gateway = true
  single_nat_gateway = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
    # Tags subnets for Karpenter auto-discovery
    "karpenter.sh/discovery" = local.name
  }

  tags = local.tags
}

 

 

locals 블록을 수정합니다.

locals {
  name   = "t101-${basename(path.cwd)}"
  region = "ap-northeast-2"

  vpc_cidr = "10.10.0.0/16"
  azs      = slice(data.aws_availability_zones.available.names, 0, 3)

  tags = {
    Blueprint  = local.name
    GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints"
  }
}

 

4. outputs.tf 확인

outputs.tf

output "configure_kubectl" {
  description = "Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig"
  value       = "aws eks --region ${local.region} update-kubeconfig --name ${module.eks.cluster_name}"
}

 

그런 다음, 다음의 명령어를 통해서 확인해 봅니다.

terraform init
tree .terraform
cat .terraform/**modules**/modules.json | jq
tree .terraform/**providers**/registry.terraform.io/hashicorp -L 2

 

본격적으로, vpc 배포를 진행해 보겠습니다.

다음의 명령어를 통해 기본 vpc를 제외한 vpc 정보를 불러옵니다.

aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

 

4. vpc 생성

 

그런 다음, module.vpc만 호출하여 vpc를 생성합니다.

terraform apply -target="module.vpc" -auto-approve

 

적용 완료된 내용을 확인합니다.

 

배포 내용도 확인해 보겠습니다.

terraform state list

 

terraform show를 통해서 적용된 tfstate 파일을 확인할 수 있습니다.

terraform show

 

다시 다음 명령을 수행하여 vpc 정보를 확인합니다.

aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

 

 

그리고 다음의 내용을 통해서 vpc를 확인해 봅니다.

echo "data.aws_availability_zones.available" | terraform console
terraform state show 'module.vpc.aws_vpc.this[0]'

 

terraform state show를 통해서 확인할 수 있는 생성된 vpc의 내용입니다.

 

다음 내용을 통해서 vpc에 매핑된 subnet을 확인합니다.

VPCID=<생성한 VPC ID>
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" | jq
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --output text
terraform state show 'module.vpc.aws_subnet.public[0]'
terraform state show 'module.vpc.aws_subnet.private[0]'

 

퍼블릭 서브넷 확인 예시 :

 

5. eks 클러스터 생성

다음의 명령어를 통해서 module.eks를 생성해 보겠습니다.

다소 시간이 걸릴 수 있으니 커피 한잔 하시면서 느긋하게 기다려 보세요!

terraform apply -target="module.eks" -auto-approve

 

앞서 설정한 outputs.tf 파일의 내용대로 출력이 되는 것을 확인하실 수 있습니다.

 

그런 다음, terraform state list를 출력해 봅니다.

terraform state list

 

하기 명령을 수행하면 클러스터 생성시 나온 내용과 동일합니다.

terraform output

 

configure_kubectl = "aws eks --region ap-northeast-2 update-kubeconfig --name t101-karpenter"

 

다음으로 EKS 자격증명을 세팅하고, context 이름을 변경하겠습니다.

aws eks --region ap-northeast-2 update-kubeconfig --name t101-karpenter
kubectl config rename-context "arn:aws:eks:ap-northeast-2:$(aws sts get-caller-identity --query 'Account' --output text):cluster/t101-karpenter" "T101-Lab"

 

다음으로 클러스터 / 노드 / 파드 정보를 확인합니다.

kubectl cluster-info
kubectl get node
kubectl get pod -A

 

이제 Fargate를 배포해 보겠습니다.

 

6. Fargate 개요

 

Cluster Autoscaler가 필요하지 않고, VM 수준의 격리가 가능하다는 것이 특징입니다. 

 

파게이트 프로파일을 통해서 파드가 사용할 서브넷, 네임스페이스, 레이블을 설정할 수 있습니다.

또한 eks 파게이트 스케줄러를 통해서 노드 스케줄링을 설정할 수 있습니다.

 

7. 리소스 배포

terraform apply -auto-approve

 

다음의 명령으로 state file을 확인합니다.

terraform state list

 

이후 배포된 클러스터와 노드 정보를 확인합니다.

kubectl cluster-info
kubectl get nodes -L node.kubernetes.io/instance-type -L topology.kubernetes.io/zone
kubectl get node -owide

 

노드 정보 확인 :

 

파드 정보도 확인해 줍니다.

kubectl get pod -A

 

배포된 헬름 차트도 확인합니다.

helm list -n karpenter

 

 

하기 명령을 통해서 karpenter helm chart에 적용된 값을 확인해 봅니다.

helm get values -n karpenter karpenter

 

 

적용된 시크릿 정보는 다음의 명령을 통해서 확인합니다.

* 이때, 암호화 적용을 활성화 한다면 비활성화가 불가하니 이 점 유의해 주시기 바랍니다.

kubectl get secret -n karpenter
kubectl get secret -n karpenter sh.helm.release.v1.karpenter.v1 -o json | jq

 

8. kube-ops-view 설치 

kube-ops-view란, 노드의 파드 상태 정보를 웹 페이지에서 실시간으로 출력할 수 있는 도구입니다.

다음의 명령어로 해당 내용을 설치해 줍니다.

 

helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system

 

그런 다음, 애플리케이션에 접근하기 위한 포트포워딩을 설정해 줍니다.

kubectl port-forward deployment/kube-ops-view -n kube-system 8080:8080 &

 

해당 터미널에서 명령을 수행하고 하기와 같은 내용이 확인되면, 웹 브라우저에서 127.0.0.1:8080으로 접속합니다.

Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
...

 

웹 브라우저에서 확인되는 화면입니다.

 

http://localhost:8080/#scale=1.5

http://localhost:8080/#scale=3

을 통해서 비율이 확대된 화면을 확인할 수 있습니다.

 

9. karpenter 개요

 

카펜터란 노드 수명 주기 관리 솔루션으로, 단 몇 초 만에 컴퓨팅 리소스 제공할 수 있다는 장점을 갖고 있는 제품입니다.

카펜터의 역할은 다음과 같습니다.

 

  1. 모니터링: Kubernetes 스케줄러가 스케줄링할 수 없는 파드를 감시합니다.
  2. 평가: 파드가 요청한 다양한 스케줄링 제약 조건을 평가합니다.
  3. 노드 프로비저닝: 파드의 요구 사항을 충족하는 새로운 노드를 생성합니다.
  4. 스케줄링: 새로 프로비저닝된 노드에 파드를 배치합니다.
  5. 노드 제거: 필요하지 않은 노드를 삭제합니다.

 

또한 현존 리소스의 용량을 평가하고 최적화된 리소스를 사용할 수 있습니다.

spot workload와 결합 시에는 15개 이상의 인스턴스 유형이 선택되어야지 중단될 위험이 적은 인스턴스를 사용할 수 있습니다.

 

10. karpenter 설치

다음과 같이 코드를 수정합니다.

---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2
  role: karpenter-t101-karpenter
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: t101-karpenter
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: t101-karpenter
  tags:
    karpenter.sh/discovery: t101-karpenter
---
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: "karpenter.k8s.aws/instance-category"
          operator: In
          values: ["c", "m", "r"]
        - key: "karpenter.k8s.aws/instance-cpu"
          operator: In
          values: ["4", "8", "16", "32"]
        - key: "karpenter.k8s.aws/instance-hypervisor"
          operator: In
          values: ["nitro"]
        - key: "karpenter.k8s.aws/instance-generation"
          operator: Gt
          values: ["2"]
  limits:
    cpu: 1000
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 30s

 

카펜터 파일을 적용합니다.

kubectl apply -f karpenter.yaml

 

배포된 내용을 확인합니다.

kubectl get ec2nodeclass,nodepool

 

example.yaml을 다음과 같이 정의합니다.

piVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      terminationGracePeriodSeconds: 0
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: 1

 

파일을 적용합니다.

kubectl apply -f example.yaml

 

적용된 내용을 확인합니다.

kubectl get deploy

 

다음과 같이 확인된다면, 

 

NAME      READY   UP-TO-DATE   AVAILABLE   AGE
inflate   0/0     0            0           42s

 

디플로이먼트의 레플리카 수를 3대로 늘리고 확인해 봅시다.

kubectl scale deployment inflate --replicas=3 && kubectl get pod -w

 

3대로 늘어난 내용을 확인할 수 있습니다.

 

11. eks-node-viewer를 통한 확인

 

eks-node-viewer를 통해서 다음과 같이 확인하실 수 있습니다.

eks-node-viewer --resources cpu,memory

 

 

eks-node-viewer는 다음의 명령어를 통해서 설치합니다.

brew tap aws/tap
brew install eks-node-viewer

 

 

12. karpenter controller log 확인

다음의 명령어를 통해서 karpenter 컨트롤러 로그를 확인해 봅시다.

kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller

 

13. 리소스 확인

kubectl get nodes -L karpenter.sh/nodepool -L node.kubernetes.io/instance-type -L topology.kubernetes.io/zone -L karpenter.sh/capacity-type

kubectl get nodeclaims
kubectl get nodeclaims -o yaml | kubectl neat

kubectl delete -f example.yaml

kubectl apply -f karpenter.yaml

 

 

14. 리소스 삭제

kube-ops-view 삭제

helm uninstall kube-ops-view -n kube-system

 

addon & karpenter helm 삭제

terraform destroy -target="module.eks_blueprints_addons" -auto-approve

 

eks 삭제

terraform destroy -target="module.eks" -auto-approve

 

VPC 삭제

terraform destroy -auto-approve

 

VPC 삭제 확인

aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

 

설정한 kubeconfig 삭제

rm -rf ~/.kube/config

 

혹은 kubectl config delete-context T101-Lab을 통해서 해당 컨텍스트만 삭제하세요!

 

 

감사합니다.

728x90
반응형