combination overview

จัดการ AWS EKS อย่างง่ายดายด้วย Terraform AWS EKS Blueprints

Deploy AWS EKS พร้อมกับเครื่องมือแบบเสร็จสรรพ

Piravit Chenpittaya
7 min readJun 20, 2024

--

ง่ายจริงไหม?

ไม่เคยใช้ AWS EKS หรอ!? ไม่มีปัญหา! คุณไม่จำเป็นต้องเข้าใจ EKS ก็ใช้ได้ เพราะ module จะจัดการให้เราเอง อย่างง่ายสุดก็คือ ก็อป default pattern หรือ gitops pattern มาใช้ได้เลย แต่อย่าลืมศึกษาเพิ่มเติมตามหลังด้วยนะ

จากที่ลองใช้มานานหลายเดือนนี่ก็ใกล้จะครบปีได้แล้ว โดยเริ่มใช้ตั้งแต่ช่วงต้นของเวอร์ชั่น 5 มีการเปลี่ยนเยอะเลยถ้าเทียบจากเวอร์ชั่น 4 หลายอย่างที่เห็นได้ชัดก็คือ แยก module ออกมาเป็นส่วนๆ ทำให้จัดการอะไรง่ายขึ้นเยอะ จากเวอร์ชั่น 4 ที่ร่วม addons กับ eks ไว้ด้วยกัน ตอนนี้ก็แยกออกจากกันแล้ว แถมมี moduleให้ใช้ custom เพิ่มเติมได้อีกด้วย

ล่าสุด AWS EKS Terraform module มีการอัพเดจใหม่อีกแล้วโดยนำ aws-auth ออก และทางทีมมีแผนจะปรับ pattern ให้ไปใช้ EKS pod identity แทน IRSA ดังนั้นใครที่อ่าน blog นี้ โค้ดไม่ใช้เวอร์ชั่นล่าสุดแล้วนะครับ พอดีผมดองไว้ลืมเพิ่งได้กลับมาเขียนต่อ555

keyword ในบทความนี้ที่จะขอละไว้ในฐานที่เข้าใจ

  • AWS EKS (Amazon Elastic Kubernetes Service) ใช้สำหรับติดตั้ง ดูแล Kubernetes ทั้งบน AWS Cloud และ on-premise
  • Terraform เป็นเครื่องมือที่ใช้จัดการ infrastructure ผ่านโค้ด (IaC) แทนที่จะทำด้วยตัวเอง (เช่นการกดผ่าน website)
  • ArgoCD เป็นเครื่องมือ GitOps continuous delivery สำหรับ Kubernetes
  • AWS services และ components อื่นๆ

Terraform EKS Blueprint คืออะไร

Terraform EKS blueprint คือ แบบแผน (PATTERN) นำมาประยุกต์ร่วมกับ Terraform เพื่อเป็นแนวทางในการขึ้นระบบและดูแล AWS EKS ได้อย่างรวดเร็วและง่ายมากๆ ความตั้งใจเดิมคือวิธีการนำระบบขึ้นมันมีหลายวิธีมากแต่ละคนก็ใช้ไม่เหมือนกันทาง AWS ก็เลยทำตัวนี้ขึ้นมาโดยแทรก Best Practice ลงไปด้วยทำให้ระหว่างที่เราใช้เจ้าตัวนี้ก็จะได้รูปแบบ Best Practice มาเลยแน่นอน แถมมาด้วยความเป็น IaC, GitOps เช่น gitops-bridge ทำให้งานที่ต้องใช้เวลาหลายวันจะเสร็จในไม่กี่ชั่วโมง

โดยตัว pattern จะนำ Terraform module ต่างๆมาประกอบกันเพื่อให้ได้ระบบที่เราต้องการเช่น

  • AWS EKS Terraform module Terraform module EKS (ตัวหลักไงละ)
  • addons สำหรับติดตั้ง addon ต่างๆ ทั้งของ AWS เอง เช่น vpc-cni, aws-ebs-csi-driver, aws loadbalancer, cluster autoscaler หรือเครื่องมือ open-source อื่นๆก็รองรับเหมือนกัน เช่น argocd, external-secrets, metrics-server ฯลฯ
  • addon (custom addon) สำหรับติดตั้ง Helm chart ที่ไม่มีใน addons และสร้าง IRSA เพิ่มเติม
  • teams สำหรับจัดการทีมบน Cluster
cluster components

จะเห็นได้ว่าเราใช้ pattern นี้ก็สามารถจัดการระบบบน EKS ได้จาก Terraform ได้เลย ไม่ว่าจะเป็นติดตั้งเครื่องมือเพิ่ม การอัพเดจ แก้ไข การตั้งค่าของ EKS และเพิ่มลด node group ได้อีกด้วย ครบจบสุดๆ

ตัว Blueprint ถูกออกแบบมาให้เราสามารถก็อปวาง pattern จากตัวอย่างแล้วนำไปใช้ได้เลยทำให้ง่ายต่อการใช้และทำให้งานเสร็จเร็วขึ้น แน่นอนว่า pattern ก็มี practice อื่นๆให้ลองมาปรับใช้กับองค์กรได้ด้วยเช่น gitops-bridge ที่ถ้าไปดู practice ล่าสุดก็คือจะให้เราสามารถ deploy เครื่องมือ open-source เช่น metrics-server, ingress-nginx ผ่านตัว argocd ทั้งหมดแทน เพื่อให้สามารถจัดการทุกอย่างได้ผ่าน GitOps (ก็ตาม practice นั้นแหละ) ลองดู pattern เพิ่มเติมได้ตรงนี้

Lets Go

Structure

Project/
├── main.tf
├── teams.tf
├── addons.tf
├── variables.tf
├── providers.tf
├── versions.tf
├── outputs.tf
└── helm_values/

ผมใช้ Structure แบบพื้นๆเลยนะครับ แต่ส่วนของโค้ดที่จะเอามาแสดงจะขอละบางส่วนนะครับเช่น output.tf, verison.tf, variables.tf และ helm_values สำหรับ boostrap ระบบนิดหน่อย

NOTE ตัวใหญ่ๆ

ในตัวอย่างผมใช้แค่ managed node group นะครับ และใช้ ArgoCD จัดการ apps ตามหลัก GitOps แต่ไม่ได้ใช้ gitops-bridge แต่คิดว่าน่าจะคล้ายกันอยู่นะ แน่นอนว่าไม่ได้ลองรายละเอียดด้านหลัง ArgoCD เยอะเลยครับเพราะมองให้เป็น pattern ดีกว่า สำหรับใครสนใจ pattern argocd แบบมี workshop ลองดู eks-blueprints-for-terraform-workshop นะครับ

ตัว provider 3 ตัวหลักจะรวมไว้ใน main ใช้สำหรับติดต่อเข้าไปใน EKS ส่วน provider อื่นๆที่จำเป็นผมละไว้ใน version จะมี aws, helm, kubernetes, kubectl, random

IMPORTAINT NOTE: ถึงเราจะกำหนด desired size ของ node group ได้ แต่สุดท้ายตัวที่ควบคุมจริงๆคือ AWS auto scaling group ดังนั้น แม้ว่าเราเปลี่ยนบน Terraform มันจะไม่ส่งผลต่อบน Cloud จริงๆ ลองอ่านเพิ่มเติมที่นี่ได้ครับ https://github.com/bryantbiggs/eks-desired-size-hack

main.tf

module หลักคือ EKS Terraform Module ใช้สร้างและกำหนด node group หรือ nodes cluster ของ EKS นั้นเอง

# Provider
provider "kubernetes" {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
token = data.aws_eks_cluster_auth.cluster_auth.token
}
provider "helm" {
kubernetes {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
token = data.aws_eks_cluster_auth.cluster_auth.token
}
}
provider "kubectl" {
apply_retry_count = 5
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
load_config_file = false
token = data.aws_eks_cluster_auth.cluster_auth.token
}
# --------
# Cluster
data "aws_region" "current" {}
data "aws_eks_cluster_auth" "cluster_auth" {
name = module.eks.cluster_name
}

module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.16"
cluster_name = var.cluster_name
cluster_version = "1.29"
cluster_endpoint_public_access = true
create_kms_key = false
cluster_encryption_config = {}
vpc_id = var.vpc_id
subnet_ids = var.subnet_ids
node_security_group_additional_rules = {
ingress_self_all = {
description = "Node to node all ports/protocols"
protocol = "-1"
from_port = 0
to_port = 0
type = "ingress"
self = true
}
}
eks_managed_node_group_defaults = {
subnet_ids = [var.subnet_ids[0]]
credit_specification = {
cpu_credits = "standard"
}
}
eks_managed_node_groups = {
general = {
name = "general"
description = "general EKS managed node group"
instance_types = ["m5a.xlarge"]
min_size = 2
max_size = 3
desired_size = 2
block_device_mappings = {
xvda = {
device_name = "/dev/xvda"
ebs = {
volume_type = "gp3"
delete_on_termination = true
}
}
}
labels = {
type = "on_demand"
}
capacity_type = "ON_DEMAND"
launch_template_tags = {
Name = "general"
}
}
spot = {
name = "spot"
description = "spot EKS managed node group"
instance_types = ["m6a.large"] # $30 monthly
min_size = 1
max_size = 1
desired_size = 1
block_device_mappings = {
xvda = {
device_name = "/dev/xvda"
ebs = {
volume_type = "gp3"
delete_on_termination = true
}
}
}
labels = {
type = "spot"
}
taints = [{
key = "type"
value = "compute-spot"
effect = "PREFER_NO_SCHEDULE"
}]
capacity_type = "SPOT"
launch_template_tags = {
Name = "spot"
}
}
}

manage_aws_auth_configmap = true
aws_auth_users = [
{
userarn = "arn:aws:iam::XXXXXXXXXXXX:user/terraform"
username = "terraform"
groups = ["system:masters"]
},
]
aws_auth_roles = flatten(
[
[
module.admin_team.aws_auth_configmap_role
module.dev_team.aws_auth_configmap_role
],
[
{
rolearn = "arn:aws:iam::XXXXXXXXXXXX:role/admin-role"
username = "admin-role"
groups = ["system:masters"]
}
]
]
)
tags = var.tags
}

ingress_self_all ผมเพิ่มไว้เพราะ default security group ของ EKS ที่ใช้สำหรับการเชื่อมต่อระหว่าง node ไม่ยอมให้ port ที่ต่ำกว่า 1025 คุยข้าม node กันได้ ตรงนี้น่าจะเพราะเป็น Best Practice ของ AWS ที่ไม่อยากให้ใช้ default port ละมั้งครับ แต่ผมขอเปิดไว้นะ555

eks_managed_node_group_defaults ใช้สำหรับค่า default ให้แก่ managed node ทั้งหมดหากไม่ได้กำหนดเอาไว้

Node Group เราสามารถตั้งได้หลากหลายเลย ตัวอย่างเช่น

  • General node (EC2 on demand) สำหรับ workload ในระบบ 2–3 nodes, instance type, EBS volume, labels, annotation
  • Spot node (EC2 spot instance) สำหรับระบบอื่นๆที่ไม่สำศัญ พร้อมกับ taints node ไว้ด้วย ที่เหลือก็ตั้งค่าได้เหมือนกับ General

ส่วนของ aws-authเป็น configmap ที่ใช้จัดการกด Authentication และ Authorization ของ Cluster (อนาคตจะไม่ใช้แล้วนะ) **เขาจะแยกออกเป็น module ใหม่ให้สามารถแก้ไขได้โดยไม่กระทบตัวหลัก**

ใน aws-auth เราสามารถเพิ่มได้ทั้งแบบ IAM user, IAM role และกำหนดว่ามีสิทธิอะไรใน Cluster ได้บ้าง ในตัวอย่างคือให้ system:admin เลย

การเพิ่ม user, role ก็ดูไม่มีปัญหาอะไร แต่ความเป็นจริงคือคนที่เข้า cluster มีหลายคน อาจจะแบ่งเป็นทีม ซึ่งสามารถจัดการโดยเพิ่มคนลง role บน AWS แล้วเพิ่ม role ใน aws-auth แทน แต่แบบนี้มันก็ไม่ใช่ IaC นะสิ ดังนั้นเลยมีอีก module ที่ชื่อว่า teams ทำให้เราสามารถสร้าง Team (IAM role) จาก users หรือ roles ที่เรากำหนดให้เลยแบบอัตโนมัติ! ลองดูด้านล่าง

teams.tf

สร้างทีม (IAM roles) จาก users, roles ที่กำหนดแล้วเอาไปใช้ต่อบน EKS ตัวอย่างเช่น สร้าง admin_team และ dev_team ต่างๆกันตรงที่ admin และ resource restriction

# https://github.com/aws-ia/terraform-aws-eks-blueprints-teams
# new team need to be added to aws_auth in eks module
# https://github.com/aws-ia/terraform-aws-eks-blueprints-teams/blob/main/tests/complete/main.tf
# https://github.com/aws-ia/terraform-aws-eks-blueprints/blob/main/patterns/multi-tenancy-with-teams/main.tf
module "admin_team" {
source = "aws-ia/eks-blueprints-teams/aws"
version = "1.1.0"
name = "admin-team"
enable_admin = true
users = [
"arn:aws:iam::XXXXXXXXXXXX:role/admin-role",
]
cluster_arn = module.eks.cluster_arn
oidc_provider_arn = module.eks.oidc_provider_arn
}

module "dev_team" {
source = "aws-ia/eks-blueprints-teams/aws"
version = "1.1.0"
name = "dev-team"
users = [
"arn:aws:iam::XXXXXXXXXXXX:user/userA",
"arn:aws:iam::XXXXXXXXXXXX:user/userB"
]
cluster_arn = module.eks.cluster_arn
oidc_provider_arn = module.eks.oidc_provider_arn

labels = {
team = "dev"
}

annotations = {
team = "dev"
}

namespaces = {
"team-dev" = {
labels = {
appName = "dev-team-app",
projectName = "project-dev",
}

resource_quota = {
hard = {
"requests.cpu" = "2000m",
"requests.memory" = "4Gi",
"limits.cpu" = "4000m",
"limits.memory" = "16Gi",
"pods" = "20",
"secrets" = "20",
"services" = "20"
}
}

limit_range = {
limit = [
{
type = "Pod"
max = {
cpu = "200m"
memory = "1Gi"
}
},
{
type = "PersistentVolumeClaim"
min = {
storage = "24M"
}
},
{
type = "Container"
default = {
cpu = "50m"
memory = "24Mi"
}
}
]
}
}
}
}

นอกจากจะกำหนดชื่อทีม คนในทีม labels, annotations ได้แล้วยังสามารถกำหนด namespace, quota ของทีมที่สามารถใช้ได้ใน Cluster ได้ตัว ละเอียดขั้นสุด

ดูเพิ่มเกี่ยวกับการตั้งค่า multi-tenancy team ได้ที่ https://github.com/aws-ia/terraform-aws-eks-blueprints/blob/main/patterns/multi-tenancy-with-teams/main.tf

addons.tf

module อีกตัวที่สำคัญสุดๆ เพราะเราจะมีแค่ Cluster เปล่าๆไม่ได้ โดยพื้นฐานก็ต้องมี coredns, kube-proxy, vpc-cni ซึ่งเป็น AWS addon นั้นเอง โดยเราสามารถติดตั้งและล็อคเวอร์ชั่นได้ด้วยเผื่อสำหรับใครไม่อยากให้อัพเดทบ่อยๆ?

# EKS Blueprints Addons
module "eks_blueprints_addons" {
# https://github.com/aws-ia/terraform-aws-eks-blueprints-addons/blob/main/main.tf
source = "aws-ia/eks-blueprints-addons/aws"
version = "1.12.0"

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

eks_addons = {
eks-pod-identity-agent = {
most_recent = true
# addon_version = "v1.3.0-eksbuild.1"
}
aws-ebs-csi-driver = {
most_recent = true
service_account_role_arn = module.ebs_csi_driver_irsa.iam_role_arn
# addon_version = "v1.28.0-eksbuild.1"
}
coredns = {
most_recent = true
# addon_version = "v1.10.1-eksbuild.7"
}
kube-proxy = {
most_recent = true
# addon_version = "v1.28.6-eksbuild.2"
}
vpc-cni = {
# Specify the VPC CNI addon should be deployed before compute to ensure
# the addon is configured before data plane compute resources are created
before_compute = true
most_recent = true # To ensure access to the latest settings provided
# addon_version = "v1.16.3-eksbuild.2"
configuration_values = jsonencode({
env = {
# Reference docs https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html
ENABLE_PREFIX_DELEGATION = "true"
WARM_PREFIX_TARGET = "1"
}
})
}
}

# Add-ons
enable_cluster_autoscaler = true
# Turn off mutation webhook for services to avoid ordering issue. see Important https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html
enable_aws_load_balancer_controller = true
aws_load_balancer_controller = {
set = [{
name = "enableServiceMutatorWebhook"
value = "false"
}]
}

# ArgoCD
enable_argocd = true
argocd = {
name = "argocd"
chart_version = "5.51.4" # v2.9.2
values = [templatefile("${path.module}/helm_values/argocd.yaml", {
AVP_VERSION = "1.16.1",
gitlab_oidc = var.gitlab_oidc
}
)]
max_history = 10
}

# Boostrap ArgoCD apps
helm_releases = {
argocd-apps = {
description = "Boostrap ArgoCD apps"
namespace = "argocd"
create_namespace = false
chart = "argocd-apps"
chart_version = "1.4.1"
repository = "https://argoproj.github.io/argo-helm"
values = [templatefile("${path.module}/helm_values/argocd-apps.yaml", {})]
}
}

tags = var.tags
}

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

role_name_prefix = "${var.cluster_name}-ebs-csi-driver-"

attach_ebs_csi_policy = true

oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
}
}

tags = var.tags
}

# Config Storage Classes
# EBS, change default storageclass from gp2 to gp3
resource "kubernetes_annotations" "gp2" {
depends_on = [module.eks_blueprints_addons]
api_version = "storage.k8s.io/v1"
kind = "StorageClass"
force = "true"

metadata {
name = "gp2"
}
annotations = {
"storageclass.kubernetes.io/is-default-class" = "false"
}
}

resource "kubernetes_storage_class_v1" "sc_gp3" {
depends_on = [module.eks_blueprints_addons]
metadata {
name = "gp3"
annotations = {
"storageclass.kubernetes.io/is-default-class" = "true"
}
}
storage_provisioner = "ebs.csi.aws.com"
reclaim_policy = "Delete"
parameters = {
type = "gp3"
fsType = "ext4"
}
volume_binding_mode = "WaitForFirstConsumer"
}

นอกจาก AWS addons แล้วในตัวอย่างก็ได้ลงเครื่องมือ open-source อื่นๆอีกเช่น cluster_autoscaler, aws_load_balancer_controller , argocdจะลงเพิ่มอีกก็ได้ให้จัดการบน Terraform ให้หมดเลย หรือไปจัดการเองบน ArgoCD ก็ได้เหมือนกัน

ถ้าเกิดว่าเครื่องมือที่เราต้องการทาง addons module ยังไม่รองรับเราสามารถ custom เองได้นะอย่างในโค้ดคือ helm_releases โดยสร้าง argocd-apps สำหรับทำการ deploy ArgoCD app ที่ต้องการบน argocd ทันทีเมื่อระบบพร้อมนั้นเอง

นอกจากนี้ก็มีการเพิ่ม StorageClass gp3 และเปลี่ยนให้เป็น default แทน gp2 ผ่าน Terraform ด้วย เพราะ default มันใช้ gp2 แต่เราอยากใช้ gp3 งะ

สรุป

AWS EKS Blueprint ใน Terraform ช่วยให้การตั้งค่าและจัดการ EKS clusters เป็นเรื่องง่ายขึ้นด้วยโมดูลที่ออกแบบไว้พร้อม Best Practice ที่ดี ทำให้การเริ่มต้นนั้นรวดเร็วและง่ายมากๆ การ scale ระบบก็ทำได้ง่าย การเชื่อมต่อกับบริการของ AWS ก็ทำได้อย่างไร้รอยต่อ ทำให้งานมีประสิทธิภาพและน่าเชื่อถือด้วย Infrastructure as Code (IaC) จึงเป็นวิธีที่แนะนำให้ใช้สำหรับใครที่ต้องจัดการงาน Kubernets บน AWS ครับ

--

--

Piravit Chenpittaya
Piravit Chenpittaya

Written by Piravit Chenpittaya

call me karn | Computer of Engineering : PSU | IG: karn.svg | git: https://github.com/karnzx /

Responses (1)