การใช้ Terraform จัดการ Kubernetes cluster ถือเป็น pattern มาตรฐานในองค์กรที่ต้องการ Infrastructure as Code แบบเต็มรูปแบบ — ตั้งแต่การ provision managed cluster (EKS, GKE, AKS) การติดตั้ง add-on สำคัญ ไปจนถึง deploy application resource ลงใน cluster ด้วย Kubernetes provider โดยตรง
บทความนี้อธิบายโครงสร้างโปรเจกต์ในการ deploy K8s cluster หลาย layer, การใช้ hashicorp/kubernetes provider + helm provider, การจัดการ state ระหว่าง cluster และ workload, รวมถึงข้อควรระวังที่ทีม platform engineering พบบ่อย
สถาปัตยกรรมแบบ Multi-Layer
โปรเจกต์ Kubernetes ใน Terraform นิยมแยก root module เป็นอย่างน้อย 3 layer เพื่อลด blast radius และทำให้ state file ไม่ใหญ่เกินไป
- Layer 1 — Network: VPC, subnet, security group, route table
- Layer 2 — Cluster: EKS/GKE/AKS control plane + node group
- Layer 3 — Workload: Namespace, Helm release, Ingress, Service, ConfigMap
แต่ละ layer มี state file แยกกัน layer ถัดไปอ่านค่าจาก layer ก่อนหน้าผ่าน terraform_remote_state data source ทำให้สามารถ apply แยกกันได้และปลอดภัยกว่าการรวมเป็น state เดียว
สร้าง EKS Cluster
ตัวอย่างใช้ module official terraform-aws-modules/eks/aws ซึ่งรวม IAM role, OIDC, node group, security group ไว้พร้อม
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "prod-cluster"
cluster_version = "1.29"
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
subnet_ids = data.terraform_remote_state.network.outputs.private_subnets
eks_managed_node_groups = {
default = {
instance_types = ["t3.medium"]
min_size = 2
max_size = 6
desired_size = 3
}
}
enable_irsa = true
}
flag enable_irsa = true สร้าง OIDC provider สำหรับ IAM Roles for Service Accounts — pod สามารถ assume IAM role ตาม namespace/SA ได้โดยไม่ต้องใส่ static credential
Kubernetes Provider
หลัง cluster พร้อม ให้ตั้งค่า provider kubernetes โดย authenticate ผ่าน exec plugin ของ AWS CLI — วิธีนี้ปลอดภัยกว่าการ hardcode token ลง state
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"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name]
}
}
การใช้ exec ให้ AWS CLI จัดการ token rotation อัตโนมัติ — ไม่ต้องอัปเดต provider config เมื่อ token หมดอายุ
จัดการ Namespace และ Resource
resource "kubernetes_namespace" "app" {
metadata {
name = "production"
labels = {
environment = "prod"
managed-by = "terraform"
}
}
}
resource "kubernetes_config_map" "app_config" {
metadata {
name = "app-config"
namespace = kubernetes_namespace.app.metadata[0].name
}
data = {
LOG_LEVEL = "info"
REGION = "ap-southeast-1"
}
}
แนะนำให้จัดการ resource ระดับ cluster (namespace, CRD, RBAC, storage class) ด้วย Terraform ส่วน application deployment ให้ใช้เครื่องมือ GitOps เช่น Argo CD หรือ Flux ซึ่งเหมาะกับ rolling update และ rollback กว่า
Helm Provider
การติดตั้ง chart platform สำคัญ เช่น ingress-nginx, cert-manager, metrics-server ผ่าน helm provider เป็นทางเลือกที่สะดวกและทำให้ dependency ระหว่าง infrastructure กับ platform service ชัดเจน
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"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name]
}
}
}
resource "helm_release" "ingress_nginx" {
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
namespace = "ingress-nginx"
version = "4.10.0"
create_namespace = true
values = [yamlencode({
controller = {
service = {
annotations = {
"service.beta.kubernetes.io/aws-load-balancer-type" = "nlb"
}
}
}
})]
}
ค่า values รับได้ทั้ง string YAML ตรง ๆ หรือใช้ yamlencode() แปลง HCL map เป็น YAML — วิธีหลังอ่านง่ายกว่าและสามารถ interpolate ค่าได้
IRSA และ Service Account
สำหรับ workload ที่ต้องการเข้าถึง AWS service (S3, DynamoDB, SQS) ให้สร้าง IAM role + service account mapping เอาไว้ล่วงหน้า
module "app_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "app-s3-reader"
role_policy_arns = {
s3 = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["production:app"]
}
}
}
resource "kubernetes_service_account" "app" {
metadata {
name = "app"
namespace = "production"
annotations = {
"eks.amazonaws.com/role-arn" = module.app_irsa.iam_role_arn
}
}
}
การจัดการ State และ Lock
- ใช้ S3 backend + DynamoDB lock แยกไฟล์ state ของแต่ละ layer — cluster/workload/network คนละ file
- ห้าม import resource ระดับ workload ที่ deploy ผ่าน Argo CD เข้ามาใน Terraform state — จะเกิด drift ตลอด
- ต้อง lifecycle
ignore_changesสำหรับ node groupdesired_sizeหาก cluster autoscaler ปรับค่าเองได้ ไม่เช่นนั้น Terraform จะ revert ทุกครั้ง
CI/CD Pipeline สำหรับ Cluster
- แยก pipeline: infra-network → infra-cluster → infra-platform (Helm add-on) → app (GitOps)
- ใช้ OIDC federation ให้ GitHub Actions assume role ตรง ๆ โดยไม่ต้องเก็บ AWS static credential
- รัน
terraform planอัตโนมัติบน PR, รันapplyหลัง merge main โดยให้มนุษย์ approve ก่อน - เปิดใช้ drift detection รายสัปดาห์ — ใช้ terraform plan เทียบ live state
ข้อควรระวัง
- Terraform kubernetes provider ไม่เหมาะกับการ deploy application ที่เปลี่ยนบ่อย — ใช้ GitOps tool แทน
- การอัปเดต cluster version ควรทำเป็นขั้น minor (1.28 → 1.29) ห้ามข้าม version
- หากลบ module EKS จะลบทุก resource ใน cluster → ตั้ง
prevent_destroy = trueที่ lifecycle ของ cluster - ควร backup etcd (ผ่าน AWS managed backup ของ EKS) หรือใช้ Velero สำหรับ workload backup
- เก็บ kubeconfig ที่ใช้ interact กับ cluster ไว้อย่างปลอดภัย อย่า commit ลง git
สรุป
Terraform เป็นเครื่องมือที่เหมาะกับการ provision Kubernetes cluster และจัดการ platform-level resource ได้อย่างมีประสิทธิภาพ — โดยเฉพาะเมื่อแยก layer ชัดเจนระหว่าง network, cluster และ platform add-on ทำให้ blast radius เล็กลง และลดโอกาสที่ apply เดียวจะกระทบ resource หลายระดับ
อย่างไรก็ดี หน้าที่ของ Terraform ควรหยุดที่ระดับ platform service ส่วน application deployment ให้ใช้ GitOps tool (Argo CD, Flux) ซึ่งออกแบบมาสำหรับ workflow นี้โดยเฉพาะ — การแยก concern แบบนี้ทำให้ทีม platform และทีม application engineer ทำงานขนานกันได้โดยไม่รบกวน state ของกันและกัน

