โครงสร้างไฟล์ Terraform โปรเจกต์แรก

เมื่อติดตั้ง Terraform และเชื่อม provider ได้แล้ว ขั้นตอนถัดไปคือการออกแบบโครงสร้างไฟล์ของโปรเจกต์ให้เป็นระเบียบตั้งแต่แรก การวางโครงสร้างที่ดีช่วยให้ทีมเปิดไฟล์แล้วรู้ทันทีว่าส่วนไหนทำอะไร ไม่ต้องไล่อ่าน config พันบรรทัดในไฟล์เดียว และยังทำให้การแยก module ในอนาคตง่ายขึ้น

บทความนี้อธิบายไฟล์มาตรฐานที่ควรมีในทุกโปรเจกต์ การตั้งชื่อ resource ที่สื่อความหมาย การจัดกลุ่มไฟล์ตามหน้าที่ และตัวอย่างโครงสร้างที่ใช้ได้จริง ตั้งแต่โปรเจกต์ขนาดเล็กจนถึงระบบ multi-environment

ไฟล์มาตรฐานในโปรเจกต์

Terraform อ่านทุกไฟล์ที่ลงท้ายด้วย .tf ภายในโฟลเดอร์เดียวกันและรวมกันเป็น configuration ชุดเดียว ดังนั้นชื่อไฟล์ไม่มีผลต่อการทำงาน แต่เป็น convention ที่ทีมทั่วโลกใช้ร่วมกัน

  • main.tf — resource หลักของโปรเจกต์
  • variables.tf — input variable ที่รับจากผู้ใช้หรือ CI/CD
  • outputs.tf — ค่าที่ export หลัง apply เสร็จ
  • providers.tf — provider block และ version pinning
  • versions.tf — required_version และ required_providers
  • locals.tf — local value สำหรับคำนวณค่าที่ใช้ซ้ำหลายที่
  • data.tf — data source ที่ดึงข้อมูลจาก provider
  • terraform.tfvars — ค่าจริงของ variable (ห้าม commit หากมี secret)

โครงสร้างโปรเจกต์ขนาดเล็ก

สำหรับโปรเจกต์เริ่มต้น 1-2 service เช่น web server + database พื้นฐาน โครงสร้างไฟล์ flat level เดียวก็เพียงพอ ไม่ต้องรีบแยก module เพราะจะทำให้ซับซ้อนเกินจำเป็น

my-project/
├── versions.tf
├── providers.tf
├── variables.tf
├── locals.tf
├── main.tf
├── outputs.tf
├── terraform.tfvars
└── .gitignore

ตัวอย่างเนื้อหาแต่ละไฟล์

ต่อไปนี้คือตัวอย่างเนื้อหาในแต่ละไฟล์ ซึ่งสร้าง AWS EC2 ขนาดเล็กพร้อม output IP address

# versions.tf
terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.60"
    }
  }
}

# providers.tf
provider "aws" {
  region = var.region
}

# variables.tf
variable "region" {
  description = "AWS region"
  type        = string
  default     = "ap-southeast-1"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

# locals.tf
locals {
  project     = "demo"
  environment = "dev"
  common_tags = {
    Project     = local.project
    Environment = local.environment
    ManagedBy   = "terraform"
  }
}

# main.tf
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  tags          = local.common_tags
}

# outputs.tf
output "public_ip" {
  value = aws_instance.web.public_ip
}

โครงสร้างโปรเจกต์ขนาดกลาง

เมื่อ resource เพิ่มขึ้นและเริ่มมีหลาย component เช่น VPC, compute, database, monitoring แนะนำให้แยกไฟล์ตาม domain ของ resource แทนรวมทุกอย่างใน main.tf

my-project/
├── versions.tf
├── providers.tf
├── variables.tf
├── locals.tf
├── network.tf      # VPC, Subnet, Route Table
├── compute.tf      # EC2, ASG, Launch Template
├── database.tf     # RDS, ElastiCache
├── security.tf     # Security Group, IAM
├── monitoring.tf   # CloudWatch
├── outputs.tf
└── terraform.tfvars

โครงสร้างโปรเจกต์ Multi-Environment

เมื่อต้องแยก dev, stage, prod ออกจากกันอย่างชัดเจน วิธีที่นิยมคือแยกโฟลเดอร์ต่อ environment และใช้ module เก็บ resource ที่ใช้ซ้ำ ทุก environment จะ include module เดียวกันแต่ส่ง variable ต่างกัน

my-project/
├── modules/
│   ├── network/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── compute/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   ├── stage/
│   │   └── ...
│   └── prod/
│       └── ...
└── README.md

.gitignore ที่ควรใช้

ไฟล์บางชนิดไม่ควรถูก commit ขึ้น git เพราะอาจมี secret หรือเป็นข้อมูล local ที่ต่างกันในแต่ละเครื่อง ให้สร้าง .gitignore ตั้งแต่ต้นโปรเจกต์

# Local .terraform directories
.terraform/
.terraform.lock.hcl  # บางทีมเก็บ, บางทีมไม่เก็บ

# State files (ควรใช้ remote backend)
*.tfstate
*.tfstate.*
*.tfstate.backup

# Crash log
crash.log
crash.*.log

# Variable files ที่มี secret
*.tfvars
*.tfvars.json
!example.tfvars     # allow example template

# Override files
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# CLI config
.terraformrc
terraform.rc

การตั้งชื่อ Resource

  • ใช้ snake_case เสมอ เช่น web_server, main_vpc ไม่ใช่ WebServer
  • ตั้งชื่อตามหน้าที่ ไม่ใช่ตาม cloud resource เช่น app ดีกว่า instance1
  • หลีกเลี่ยงตัวเลขต่อท้ายถ้าไม่จำเป็น ใช้ count หรือ for_each แทน
  • ห้ามตั้งชื่อซ้ำ type เช่น resource "aws_instance" "aws_instance"
  • ชื่อ module ควรใช้ kebab-case เช่น network-vpc

เอกสารภายในโปรเจกต์

ไฟล์ README.md ที่ดีช่วยให้คนใหม่เข้าโปรเจกต์แล้วเข้าใจได้เร็ว ควรระบุหัวข้อต่อไปนี้: วัตถุประสงค์ของ infrastructure, prerequisites (Terraform version, provider, credentials), วิธี init/plan/apply, ตัวแปรสำคัญที่ต้องตั้ง, และจุดที่อาจต้องระวังก่อน apply บน production

สรุป

การวางโครงสร้างไฟล์ตั้งแต่เริ่มต้นช่วยลดหนี้ทางเทคนิคในระยะยาว ทุกโปรเจกต์ควรมีไฟล์มาตรฐาน versions, providers, variables, main, outputs เป็นอย่างน้อย เมื่อ resource มากขึ้นให้แยกตาม domain และเมื่อต้องรองรับหลาย environment ให้ใช้ module กับโฟลเดอร์ env แยก การตั้งชื่อ resource ด้วย snake_case ที่สื่อความหมาย และการเขียน README ช่วยให้ทีมทำงานต่อได้สะดวก ขั้นตอนต่อไปหลังวางโครงสร้างเสร็จคือเรียนรู้เรื่อง resource กับ data source เพื่อเริ่มสร้าง infrastructure จริง