home..

Provisioning EKS with Terraform

Kubernetes EKS AEWS Gasida Cloudnet IaC Terraform

테라폼으로 EKS 배포하기

EKS 클러스터 배포

Terraform을 사용하기 위한 선수조건이 만족되어있는 기준으로 프로비저닝을 진행합니다. 코드는 gasida님 github에 있는 코드를 기준으로 프로비저닝을 진행합니다.

프로비저닝을 위해 TL;DR 개념으로 코드를 apply 하는 과정을 먼저 보여드리고 프로비저닝 하는 옵션에 대해서는 글 하단부에 별도로 설명을 진행했습니다.

# 코드 가져오기
git clone https://github.com/gasida/aews-cicd.git
cd aews-cicd/4

# terraform 환경 변수 저장
export TF_VAR_KeyName=[각자 ssh keypair]
echo $TF_VAR_KeyName

# 
terraform init
terraform plan

# 10분 후 배포 완료
terraform apply -auto-approve

테라폼 정보 확인

#
terraform state list
~~~
#
terraform console
-----------------
data.aws_caller_identity.current
data.aws_caller_identity.current.arn
data.aws_eks_cluster.cluster
data.aws_eks_cluster.cluster.name
data.aws_eks_cluster.cluster.version
data.aws_eks_cluster.cluster.access_config
data.aws_eks_cluster_auth.cluster
kubernetes_service_account.aws_lb_controller
-----------------
terraform state show ...

#
cat terraform.tfstate | jq 
more terraform.tfstate

배포 정보 확인

#
kubectl get node -v=6

# EKS 클러스터 인증 정보 업데이트
CLUSTER_NAME=myeks
aws eks update-kubeconfig --region ap-northeast-2 --name $CLUSTER_NAME
kubectl config rename-context "arn:aws:eks:ap-northeast-2:$(aws sts get-caller-identity --query 'Account' --output text):cluster/$CLUSTER_NAME" "Aews-Labs"

#
kubectl cluster-info
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -A

이 상태에서 코드 재활용해서 두번째 클러스터 배포하기

#
cd ..
mkdir 5
cd 5
cp ../4/*.tf .
ls

#
terraform init
terraform apply -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"

# EKS 클러스터 인증 정보 가져오기
CLUSTER_NAME2=myeks2
aws eks update-kubeconfig --region ap-northeast-2 --name $CLUSTER_NAME2 --kubeconfig ./myeks2config

# EKS 클러스터 정보 확인
kubectl --kubeconfig ./myeks2config get node 
kubectl --kubeconfig ./myeks2config get pod -A

Untitled

같은 코드로 클러스터 두개의 배포에 성공했습니다.

이제 다시 삭제해주도록 하겠습니다

# 첫번째 폴더에서 클러스터 삭제
terraform destroy -auto-approve

# 두번째 폴더에서  클러스터 삭제
terraform destroy -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"

개인적으로 테라폼을 적극적으로 사용해보질 않아

aews cicd git에 있는 terraform 코드를 한번씩 살펴보겠습니다.

Untitled

구조는 굉장히 간단하네요.

어디에 어떻게 사용될지 모르는 변수 항목을 먼저 보겠습니다.

뒤에서 참조하려면 꼭 선언되어있어야 할테니깐요

variable "KeyName" {
  description = "Name of an existing EC2 KeyPair to enable SSH access to the instances."
  type        = string
}

variable "ClusterBaseName" {
  description = "Base name of the cluster."
  type        = string
  default     = "myeks"
}

variable "KubernetesVersion" {
  description = "Kubernetes version for the EKS cluster."
  type        = string
  default     = "1.29"
}

variable "WorkerNodeInstanceType" {
  description = "EC2 instance type for the worker nodes."
  type        = string
  default     = "t3.medium"
}

variable "WorkerNodeCount" {
  description = "Number of worker nodes."
  type        = number
  default     = 3
}

variable "WorkerNodeVolumesize" {
  description = "Volume size for worker nodes (in GiB)."
  type        = number
  default     = 30
}

variable "TargetRegion" {
  description = "AWS region where the resources will be created."
  type        = string
  default     = "ap-northeast-2"
}

variable "availability_zones" {
  description = "List of availability zones."
  type        = list(string)
  default     = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
}

variable "VpcBlock" {
  description = "CIDR block for the VPC."
  type        = string
  default     = "192.168.0.0/16"
}

variable "public_subnet_blocks" {
  description = "List of CIDR blocks for the public subnets."
  type        = list(string)
  default     = ["192.168.1.0/24", "192.168.2.0/24", "192.168.3.0/24"]
}

variable "private_subnet_blocks" {
  description = "List of CIDR blocks for the private subnets."
  type        = list(string)
  default     = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
}

VPC CIDR 블록과 서브넷들의 변수가 선언되어있고

인스턴스의 정보들을 선언하고

접근할 자격증명, 그리고 쿠버네티스의 버전 명시가 되어있네요

우선 모든 클라우드 리소스의 표준이 되는 네트워크 VPC부터 보겠습니다.

vpc.tf

provider "aws" {
  region = var.TargetRegion
}

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

  name = "${var.ClusterBaseName}-VPC"
  cidr = var.VpcBlock
  azs  = var.availability_zones

  enable_dns_support   = true
  enable_dns_hostnames = true

  public_subnets  = var.public_subnet_blocks
  private_subnets = var.private_subnet_blocks

  enable_nat_gateway = true
  single_nat_gateway = true
  one_nat_gateway_per_az = false
  
  map_public_ip_on_launch = true
 
  igw_tags = {
    "Name" = "${var.ClusterBaseName}-IGW"
  }
 
  nat_gateway_tags = {
    "Name" = "${var.ClusterBaseName}-NAT"
  }
 
  public_subnet_tags = {
    "Name"                     = "${var.ClusterBaseName}-PublicSubnet"
    "kubernetes.io/role/elb"   = "1"
  }

  private_subnet_tags = {
    "Name"                             = "${var.ClusterBaseName}-PrivateSubnet"
    "kubernetes.io/role/internal-elb" = "1"
  }

  tags = {
    "Environment" = "${var.ClusterBaseName}-lab"
  }
}

vpc 모듈을 사용하는데 있어 최소 버전명시를 진행하고

var.tf에서 사용했던 서브넷 그리고 NAT Gateway 설정을 진행합니다.

NAT GW가 NAT Instance에 비해 세배정도 비싸긴하지만,

multi AZ에서 NAT GW자체를 한개만 생성하기위해 single nat gw를 선언했습니다.

기초를 다시 다질겸 적어보자면

IGW는 Public Subnet 을 위한 리소스로 인/아웃바운드가 모두 가능하며

NAT GW는 Private Subnet을 위한 리소스로 아웃바운드만 가능합니다.

다음은 eks.tf 파일입니다

data "aws_caller_identity" "current" {}

resource "aws_iam_policy" "external_dns_policy" {
  name        = "${var.ClusterBaseName}ExternalDNSPolicy"
  description = "Policy for allowing ExternalDNS to modify Route 53 records"

  policy = jsonencode({
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "route53:ChangeResourceRecordSets"
        ],
        "Resource": [
          "arn:aws:route53:::hostedzone/*"
        ]
      },
      {
        "Effect": "Allow",
        "Action": [
          "route53:ListHostedZones",
          "route53:ListResourceRecordSets"
        ],
        "Resource": [
          "*"
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "external_dns_policy_attach" {
  role       = "${var.ClusterBaseName}-node-group-eks-node-group"
  policy_arn = aws_iam_policy.external_dns_policy.arn

  depends_on = [module.eks]
}

resource "aws_security_group" "node_group_sg" {
  name        = "${var.ClusterBaseName}-node-group-sg"
  description = "Security group for EKS Node Group"
  vpc_id      = module.vpc.vpc_id

  tags = {
    Name = "${var.ClusterBaseName}-node-group-sg"
  }
}

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

  cluster_name = var.ClusterBaseName
  cluster_version = var.KubernetesVersion
  cluster_endpoint_private_access = false
  cluster_endpoint_public_access  = true

  cluster_addons = {
    coredns = {
      most_recent = true
    }
    kube-proxy = {
      most_recent = true
    }
    vpc-cni = {
      most_recent = true
    }
  }

  vpc_id = module.vpc.vpc_id
  enable_irsa = true
  subnet_ids = module.vpc.public_subnets

  eks_managed_node_groups = {
    default = {
      name             = "${var.ClusterBaseName}-node-group"
      use_name_prefix  = false
      instance_type    = var.WorkerNodeInstanceType
      desired_size     = var.WorkerNodeCount
      max_size         = var.WorkerNodeCount + 2
      min_size         = var.WorkerNodeCount - 1
      disk_size        = var.WorkerNodeVolumesize
      subnets          = module.vpc.public_subnets
      key_name         = var.KeyName
      vpc_security_group_ids = [aws_security_group.node_group_sg.id]
      iam_role_name    = "${var.ClusterBaseName}-node-group-eks-node-group"
      iam_role_use_name_prefix = false
      iam_role_additional_policies = {
        "${var.ClusterBaseName}ExternalDNSPolicy" = aws_iam_policy.external_dns_policy.arn
      }
   }
  }

  access_entries = {
    admin = {
      kubernetes_groups = []
      principal_arn     = "${data.aws_caller_identity.current.arn}"

      policy_associations = {
        myeks = {
          policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
          access_scope = {
            namespaces = []
            type       = "cluster"
          }
        }
      }
    }
  }

  tags = {
    Environment = "${var.ClusterBaseName}-lab"
    Terraform   = "true"
  }
}

external dns 를 사용하기위해 선언한 iam policy, polciy attachment

그리고 노드그룹을 선언했습니다.

그리고 EKS를 선언하는데

eks module 사용을 위한 최소버전명시

EKS Provisioning 시간에 배웠던 cluster endpoint 설정과

애드온으로 coredns, kube-proxy, vpc-cni의 최신버전 설치를 진행합니다.

예전에 찾아봤었을때는 EBS, EFS CSI DRIVER는 most recent 파라미터가 없었던걸로 기억이되는데 맞는지 확인을 다시 한번 해봐야겠네요.

vpc 설정과 서브넷 설정을 진행하고

irsa 활성화를하는데, 이 값들은 다음 파일에서 다룹니다.

마지막으로 access_entries 코드가 있는데

뭐지 모르겠어서 검색을해보니 eksctl 사이트에서 정보 제공을 해주네요

https://eksctl.io/usage/access-entries/

Untitled

[A deep dive into simplified Amazon EKS access management controls Amazon Web Services](https://aws.amazon.com/ko/blogs/containers/a-deep-dive-into-simplified-amazon-eks-access-management-controls/)

aws doc의 정보도 같이 첨부해봤습니다.

이제 다시보니 인증/인가 다룰때 공부했던 신기능이네요

configmap에서 기본 설정을 잘못지우면 클러스터에 접근권한을 완벽하게 상실되는 케이스를 보면서 이를 방지하기 위한 기능이였습니다.

Untitled

이런데서 실수로 건들다간…

마지막으론 IRSA입니다

irsa.tf

locals {
  cluster_oidc_issuer_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(module.eks.cluster_oidc_issuer_url, "https://", "")}"
}

module "eks-external-dns" {
  source = "DNXLabs/eks-external-dns/aws"
  version = "0.2.0"

  cluster_name                     = var.ClusterBaseName
  cluster_identity_oidc_issuer     = module.eks.cluster_oidc_issuer_url
  cluster_identity_oidc_issuer_arn = local.cluster_oidc_issuer_arn

  enabled = false

}

module "aws_load_balancer_controller_irsa_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "5.3.1"

  role_name = "${var.ClusterBaseName}-aws-load-balancer-controller"

  attach_load_balancer_controller_policy = true

  oidc_providers = {
    ex = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
    }
  }
}

provider "kubernetes" {
  host                   = module.eks.cluster_endpoint
  token                  = data.aws_eks_cluster_auth.cluster.token
  cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
}

data "aws_eks_cluster" "cluster" {
  name = module.eks.cluster_name
  depends_on = [module.eks]
}

data "aws_eks_cluster_auth" "cluster" {
  name = module.eks.cluster_name
  depends_on = [module.eks]
}

resource "kubernetes_service_account" "aws_lb_controller" {
  metadata {
    name      = "aws-load-balancer-controller"
    namespace = "kube-system"
    annotations = {
      "eks.amazonaws.com/role-arn" = module.aws_load_balancer_controller_irsa_role.iam_role_arn
    }
  }
}
© 2024 mont kim   •  Powered by Soopr   •  Theme  Moonwalk