2024. 7. 27. 23:35ㆍEKS@Terraform
- CloudNet에서 주관하는 Terraform 스터디 내용입니다
- 내용은 위 책 테라폼으로 시작하는 IaC 를 기준으로 정리하였습니다.
- 실습은 M1 macbook air 에서 진행했습니다.
- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다
- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!
Amazon EKS Blueprints for Terraform
AWS는 Terraform을 사용해서 EKS를 운영하는 사례를 Amazon EKS Blueprints for Terraform 에서 제공합니다. 이 중 Karpenter 적용한 EKS cluster 를 Fargate 서버리스 클러스터에서 올리는 실습을 해보려고 합니다.
실습을 시작하기 전 여기에서 적용한 서비스와 개념들을 하나씩 간단하게 소개해보겠습니다
Karpenter
karpenter는 k8s native한 노드 수명 주기 관리 솔루션(오토스케일러) 입니다. 카펜터 말고도 기존에 사용되던 오토스케일러(HPA, VPA) 가 있었지만 새 리소스 생성 및 반영에 상대적으로 긴 시간(2-3분) 이 걸렸는데, 카펜터 실습에서는 리소스 변경이 수 초 안에 반영되어 매우 놀랐던 기억이 있습니다. 트래픽 급증에 보다 순발력 있게 대응할 수 있을 것 같습니다.
지난번 EKS 스터디에서 Karpenter 활용 실습을 진행한 내용이 있으니 자세한 내용이 필요하다면 이쪽으로..ㅎㅎ
[EKS@CloudNet] EKS Autoscaling: Karpenter 외
- CloudNet에서 주관하는 EKS 스터디 2기 내용입니다 - 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다 Autoscaling이란? 개인적으로 오토스케일링이 클라우드 컴퓨팅의 가장 큰 존재 이유라
hitherex.tistory.com
Fargate
AWS가 제공하는 서버리스 환경입니다. VM수준의 격리 기능을 제공하며, 컨테이너 이미지 빌드 후 적절한 리소스를 생성해 줍니다.
EKS를 배포한다고 가정하면 어떤 인스턴스를 노드에 올릴지, 오토스케일링은 어떻게 할지 등을 고민해야 하는데 Fargate를 사용하면 Fargate profile(subnet, namespace, label conditions) 을 지정만 해 주면 되어 편리합니다.
스팟 인스턴스
저는 주변의 비개발자에게 AWS를 공유오피스 사업자 내지는 임대업자(ㅋㅋㅋㅋ) 라고 소개합니다. 아무래도 AWS의 장점은 내가 원하는 스펙의 서버를 원하는 기간 동안 사용할 수 있다는 것인데요, 건물주에 비유해보면 AWS는 그만큼의 공실 부담을 지게 됩니다. 오피스와 달리 인스턴스는 임대와 회수가 매우 간편하며 즉각적이라는 점을 이용해 빈 인스턴스를 미리 예약해둔 유저에게 싼값에 빌려주는 대신 정가에 쓰겠다는 사용자가 요청하면 종료하는 것이 spot instance 입니다. 이때 종료알림 2분 후 인스턴스가 종료되므로 이 시간 안에 필요 데이터를 백업해야 합니다.
서울 리전에서 가장 비싼 인스턴스인 p4d.24xlarge를 예시로 들어보겠습니다. A100(VRAM 40GB) GPU 무려 8장이 붙어있는 인스턴스입니다. 그만큼 가격도 대단해서 온디맨드로 요청하면 시간당 $45가 부과됩니다💸
그러나 스팟인스턴스로 요청하면, 요금이 시간당 $7.9로 확 낮아지게 됩니다(대신 내가 원할 때 사용할 수 있다는 보장은 없습니다)
그러나 실제 스팟인스턴스 요청 화면에서는 사용자가 스팟인스턴스로 비딩(bidding) 할 가격과 기간을 지정할 수 있습니다. 적당한 요금을 비딩해서 스스로의 우선순위를 조정할 수 있겠습니다.
실습
우선 EKS를 terraform 으로 fargate에 배포 해 보겠습니다
사전 준비로 aws cli, admin권한을 가진 IAM, terraform, kubectl, helm 이 필요합니다.
그리고 github에서 실습코드를 클론해 옵니다.
git clone https://github.com/aws-ia/terraform-aws-eks-blueprints
cd terraform-aws-eks-blueprints/patterns/karpenter
이 테라폼 코드는 us-west-1 기준으로 작성되어 있기 때문에 서울리전에서 사용하기 위해서는 main.tf 의 local 블록을 수정해야 합니다.
가시다님께서 해당 부분을 수정한 테라폼 파일을 공유해주셔서 사용했습니다. 아래 토글에 있으니 필요하신 분은 참고하세요!
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
}
전체를 배포하는 데 대략 15~20분 정도 소요됩니다 (vpc 배포 3분, eks 배포 13분)
terraform apply -target="module.vpc" -auto-approve
terraform apply -target="module.eks" -auto-approve
추가로 addon 을 설치합니다
terraform apply -auto-approve
저는 tag 없이 apply 해서 모두 한꺼번에 설치했습니다.
이번에는 AWS GUI console 에서 배포된 인스턴스를 확인해보겠습니다.
기존 EKS 스터디에서는 EC2 인스턴스를 노드로 사용했었는데 이번에는 fargate 로 배포했기 때문에 배포된 EC2는 없는 것을 볼 수 있습니다
대신 EKS 메뉴에 들어가면 인스턴스 유형이 'Fargate' 로 지정되어 4개의 노드가 배포된 것을 볼 수 있습니다
karpenter 실습
현재 클러스터에서는 karpenter를 이용해서 노드를 관리하고 있는데, 이 karpenter 를 이용해서 리소스가 추가 배포되었을 때의 액션을 확인해 보겠습니다. 현재 작업중인 경로에는 karpenter.yaml 이 있지만 우리의 실습에 맞게 클러스터 이름을 바꿔놓은 만큼 해당부분을 변령해줘야 합니다.
아래와 같이 karpenter.yaml 파일을 수정해줍니다.
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
그리고 example.yaml 을 잠시 살펴보겠습니다. inflate 라는 deployment 는 처음에는 0개의 Replicaset 으로 지정되지만 replicaset이 그렇듯 갯수를 scale up/down 할 수 있습니다. 각 pod는 cpu를 1코어씩 점유하게 되므로 노드의 오토스케일링을 강제하게 됩니다
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
+DEBUG 이전/이후의 노드 개수를 확인해보기 위해 kubectl 명령어를 쳤는데 클러스터에 접근할 수 없다는 에러메시지가 발생했습니다. 분명히 클러스터를 배포했는데 다른 kubectl 명령어도 먹히지 않아 확인해보니 EKS가 아닌 로컬 클러스터에서 kubectl 명령어를 실행하려고 하니 발생하는 문제였습니다
따라서 cli에 아래 명령을 입력해주어 t101-karpenter 로 kubectl 명령어가 들어가게 설정했습니다.
aws eks --region ap-northeast-2 update-kubeconfig -
-name t101-karpenter
다른 스터디 멤버분의 블로그를 참고해 해결할 수 있었습니다. 감사드립니다!
이제 EKS cluster로 접근해서 fargate node 가 4개 배포되었음을 cli로도 확인할 수있습니다
클러스터에 karpenter와 example Deployment 를 배포해 줍니다
kubectl apply -f karpenter.yaml
kubectl apply -f example.yaml
karpenter 가 관리하는 nodepool과 ec2nodeclass 가 잘 배포되었음을 볼 수 있습니다.
nodepool은 실제 프로비저닝 동작을 제어하며, ec2nodeclass 를 참조하여 생성할 노드를 결정합니다
ec2nodeclass 는 노드의 기본 구성과 인스턴스의 세부사항을 지정합니다
example deployment 도 잘 배포되었습니다. 그렇지만 replicaset 의 갯수가 default=0으로 지정되어 있으므로 정상적으로 배포된 상태가 맞습니다. 부끄럽지만 yaml을 확인하지 않고 어 왜 배포가 안되지?! 하고서야 확인했습니다.
그렇다면 replica의 갯수를 늘리면서 동작의 변화를 보겠습니다. 아까 스펙에서 컨테이너 1개당 1core를 필요로 한다고 했기 때문에 3코어를 추가로 필요로 하게 됩니다.
kubectl scale deployment inflate --replicas=3
3개가 성공적으로 배포되었습니다
다시 node를 조회해 보면 스펙 변경에 대응하기 위해 1개가 추가된 것을 볼 수 있습니다.
리소스 삭제
helm uninstall kube-ops-view -n kube-system
terraform destroy -target="module.eks_blueprints_addons" -auto-approve
terraform destroy -target="module.eks" -auto-approve
terraform destroy -auto-approve
# vpc 삭제는 꼭 더블체크
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml
rm -rf ~/.kube/config
이렇게 편리하게 스펙변경에 대응할 수 있다는 점이 Fargate로 배포한 EKS 를 Terraform 으로 관리할 때 장점이라고 생각합니다. 정말 AWS에는 다양한 서비스가 있다는 것을 깨달았고 보다 더 자유자재로 쓰기 위해 정!진! 하겠습니다 🙃
'EKS@Terraform' 카테고리의 다른 글
[Terraform@CloudNet] Terraform과 OpenTofu (0) | 2024.08.03 |
---|---|
[Terraform@CloudNet] Terraform Runner: Atlantis (0) | 2024.07.13 |
[Terraform@CloudNet] Terraform Module (0) | 2024.07.13 |
[Terraform@CloudNet] Terraform State (0) | 2024.07.07 |
[Terraform@CloudNet] Terraform Provider (0) | 2024.07.06 |