Terraform Best Practices: โครงสร้าง Project และการจัดระเบียบ Code

เมื่อ project HCL เริ่มใหญ่ขึ้น การจัดระเบียบโครงสร้างให้ดีเป็นสิ่งสำคัญ — ถ้าไฟล์กระจัดกระจายหรือตั้งชื่อมั่วจะทำให้ทีมทำงานร่วมกันได้ยาก, review code ช้า, และเสี่ยงเกิด drift ระหว่าง environment

บทความนี้รวบรวม best practices สำหรับการจัดระเบียบโครงสร้าง project ที่ใช้งานจริงในทีม — ตั้งแต่การตั้งชื่อไฟล์ การแยก environment การจัด module และการทำให้ code อ่านง่ายและ maintain ได้ในระยะยาว

โครงสร้างพื้นฐานของ Project

โครงสร้างมาตรฐานที่แนะนำในทุก project (ไม่ว่าขนาดเล็กหรือใหญ่):

project-root/
├── main.tf              # resource หลัก
├── variables.tf         # input variable ทั้งหมด
├── outputs.tf           # output value ทั้งหมด
├── versions.tf          # required_providers, required_version
├── locals.tf            # local values (ถ้ามี)
├── terraform.tfvars     # ค่า variable สำหรับ env ปัจจุบัน
├── README.md            # อธิบายการใช้
└── .gitignore           # ignore state, .terraform/

การตั้งชื่อไฟล์ (File Naming)

ใช้ชื่อไฟล์ตามหน้าที่ (role-based) ไม่ใช่ตาม technology — ทำให้อ่านง่ายกว่า

  • main.tf — resource หลักของ config นี้
  • variables.tf — ทุก variable block รวมที่เดียว
  • outputs.tf — ทุก output block รวมที่เดียว
  • versions.tfterraform {} block (required_version, required_providers)
  • locals.tflocals {} block (ถ้ามีหลายตัว)
  • providers.tfprovider block (ถ้าไม่ต้องการปนกับ versions.tf)

สำหรับ project ใหญ่ที่ main.tf ยาวเกินไป ให้แยกตาม domain: network.tf, compute.tf, database.tf, iam.tf — หลีกเลี่ยงการแยกตาม resource ตัวเดียว (เช่น vpc.tf, subnet.tf) เพราะจะมีไฟล์เยอะเกินจำเป็น

แยก Environment (dev / staging / prod)

มี 2 แนวทางหลัก — Workspace หรือ Separate Directory

แนวทาง 1: Separate Directory (แนะนำสำหรับทีมใหญ่)

infrastructure/
├── modules/
│   ├── vpc/
│   ├── ec2-cluster/
│   └── rds/
└── environments/
    ├── dev/
    │   ├── main.tf
    │   ├── variables.tf
    │   ├── terraform.tfvars
    │   └── backend.tf
    ├── staging/
    │   ├── main.tf
    │   ├── ...
    └── prod/
        ├── main.tf
        ├── ...
  • ข้อดี — แยก state ชัดเจน, เปลี่ยน config prod โดยไม่กระทบ dev, control access ต่อ directory ได้
  • ข้อเสีย — ต้องระวัง drift ถ้าแก้ config ซ้ำกันหลาย env (แก้ dev ลืมแก้ prod)

แนวทาง 2: Workspace (เหมาะกับ project เล็ก)

terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# ใน code:
resource "aws_instance" "web" {
  instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
}
  • ข้อดี — ใช้ code เดียวจัดการหลาย env, ง่ายสำหรับ project เล็ก
  • ข้อเสีย — เสี่ยง apply ผิด workspace, ไม่เหมาะกับ config ที่ต่างกันมากระหว่าง env

Module Organization

โครงสร้าง module ที่แนะนำ:

modules/
├── vpc/
│   ├── main.tf          # resource ของ module
│   ├── variables.tf     # input
│   ├── outputs.tf       # output
│   ├── versions.tf      # required_providers
│   └── README.md        # วิธีใช้ + ตัวอย่าง
├── ec2-cluster/
│   ├── ...
└── rds/
    ├── ...
  • แต่ละ module ควรทำหน้าที่เดียวที่ชัดเจน (Single Responsibility)
  • ถ้า module ใหญ่เกินไป ให้แยกเป็น module ย่อย (submodules) แทน
  • versions.tf ใน module ควรระบุเฉพาะ required_providers — ไม่กำหนด required_version เฉพาะเจาะจง (ให้ root module ควบคุม)

Variable และ Output ที่ดี

  • ทุก variable ต้องมี description — บอกว่าใช้ทำอะไร
  • ใช้ type ทุกครั้ง (string, number, list, map, object) — ไม่ใช่ปล่อยเป็น any
  • มี default สำหรับค่าที่ optional — required variable ต้องไม่มี default
  • ใช้ validation block สำหรับ input ที่มี rule เฉพาะ (เช่น ต้องเริ่มด้วย “prod-“)
  • กำหนด sensitive = true สำหรับ value ที่ไม่ควร print ลง log
variable "environment" {
  type        = string
  description = "Deployment environment (dev, staging, prod)"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be one of: dev, staging, prod"
  }
}

variable "db_password" {
  type        = string
  description = "Database admin password"
  sensitive   = true
}

Naming Convention

  • Resource name: ใช้ snake_case เสมอ (เช่น web_server ไม่ใช่ WebServer)
  • ชื่อ resource ควรบอกบทบาท ไม่ต้องซ้ำ type — ผิด: resource "aws_instance" "aws_instance_web"ถูก: resource "aws_instance" "web"
  • ใช้ prefix/suffix แสดง environment ใน tag (ไม่ใช่ในชื่อ resource) — เช่น Name = "${var.environment}-web-server"
  • ชื่อ variable ใช้ snake_case, อธิบายหน้าที่ชัดเจน: db_instance_class ไม่ใช่แค่ type

ใช้ Tag Consistent

กำหนด common tag ที่ locals แล้ว merge กับ tag เฉพาะของแต่ละ resource

locals {
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
    Owner       = var.team_email
  }
}

resource "aws_instance" "web" {
  # ...
  tags = merge(local.common_tags, {
    Name = "${var.environment}-web-server"
    Role = "frontend"
  })
}

ควบคุม Provider Version

pin version ของ HCL CLI และ provider ไว้เสมอ เพื่อให้ทีม deploy ได้ผลลัพธ์เหมือนกัน

# versions.tf
terraform {
  required_version = "~> 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.30"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }
}

.gitignore ที่จำเป็น

# Terraform
.terraform/
.terraform.lock.hcl.bak
*.tfstate
*.tfstate.*
*.tfvars.local
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# IDE
.idea/
.vscode/

ยกเว้น.terraform.lock.hcl ต้อง commit (ไม่ใส่ใน .gitignore) เพื่อให้ provider version lock ตรงกันทั้งทีม

README.md ที่ดี

ควรมีอย่างน้อย:

  • อธิบาย project ทำอะไร deploy อะไรบ้าง
  • Prerequisite (HCL version, provider credentials, AWS account)
  • วิธี init / plan / apply สำหรับแต่ละ environment
  • รายการ variable หลัก (หรือลิงก์ไป terraform-docs ที่ generate อัตโนมัติ)
  • Troubleshooting พื้นฐาน

Best Practices สรุป

  • ใช้โครงสร้างไฟล์มาตรฐาน (main/variables/outputs/versions) ทุก project
  • แยก environment ด้วย directory สำหรับทีมใหญ่ ใช้ workspace เฉพาะ project เล็ก
  • Module ต้องมี single responsibility + README อธิบายการใช้งาน
  • Variable ต้องมี description, type, validation (ถ้ามี rule)
  • Naming: snake_case, บอกบทบาท, ไม่ซ้ำ type
  • ใช้ common tag ผ่าน locals + merge
  • Pin version ของ HCL CLI และ provider
  • commit .terraform.lock.hcl แต่ ignore state file และ .terraform/
  • เขียน README ให้คนใหม่เริ่มทำงานได้เอง

สรุป

การจัดระเบียบโครงสร้าง project ให้ดีเป็นรากฐานของการใช้งาน HCL ในระดับทีม — โครงสร้างไฟล์มาตรฐาน, การแยก environment ที่ชัดเจน, module ที่มี single responsibility และ naming convention ที่เป็นระเบียบช่วยให้ทีม onboard คนใหม่ได้เร็วและลดความเสี่ยง config drift ในบทความถัดไปจะพูดถึง code style และการ format code ด้วย terraform fmt เพื่อให้ code อ่านง่ายและสม่ำเสมอทั้งทีม