Provisioning EKS with Terraform
April 2024 (1025 Words, 6 Minutes)
테라폼으로 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
같은 코드로 클러스터 두개의 배포에 성공했습니다.
이제 다시 삭제해주도록 하겠습니다
# 첫번째 폴더에서 클러스터 삭제
terraform destroy -auto-approve
# 두번째 폴더에서 클러스터 삭제
terraform destroy -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"
개인적으로 테라폼을 적극적으로 사용해보질 않아
aews cicd git에 있는 terraform 코드를 한번씩 살펴보겠습니다.
구조는 굉장히 간단하네요.
어디에 어떻게 사용될지 모르는 변수 항목을 먼저 보겠습니다.
뒤에서 참조하려면 꼭 선언되어있어야 할테니깐요
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/
[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에서 기본 설정을 잘못지우면 클러스터에 접근권한을 완벽하게 상실되는 케이스를 보면서 이를 방지하기 위한 기능이였습니다.
이런데서 실수로 건들다간…
마지막으론 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
}
}
}