เมื่อติดตั้ง Terraform และเชื่อม provider ได้แล้ว ขั้นตอนถัดไปคือการออกแบบโครงสร้างไฟล์ของโปรเจกต์ให้เป็นระเบียบตั้งแต่แรก การวางโครงสร้างที่ดีช่วยให้ทีมเปิดไฟล์แล้วรู้ทันทีว่าส่วนไหนทำอะไร ไม่ต้องไล่อ่าน config พันบรรทัดในไฟล์เดียว และยังทำให้การแยก module ในอนาคตง่ายขึ้น
บทความนี้อธิบายไฟล์มาตรฐานที่ควรมีในทุกโปรเจกต์ การตั้งชื่อ resource ที่สื่อความหมาย การจัดกลุ่มไฟล์ตามหน้าที่ และตัวอย่างโครงสร้างที่ใช้ได้จริง ตั้งแต่โปรเจกต์ขนาดเล็กจนถึงระบบ multi-environment
ไฟล์มาตรฐานในโปรเจกต์
Terraform อ่านทุกไฟล์ที่ลงท้ายด้วย .tf ภายในโฟลเดอร์เดียวกันและรวมกันเป็น configuration ชุดเดียว ดังนั้นชื่อไฟล์ไม่มีผลต่อการทำงาน แต่เป็น convention ที่ทีมทั่วโลกใช้ร่วมกัน
main.tf— resource หลักของโปรเจกต์variables.tf— input variable ที่รับจากผู้ใช้หรือ CI/CDoutputs.tf— ค่าที่ export หลัง apply เสร็จproviders.tf— provider block และ version pinningversions.tf— required_version และ required_providerslocals.tf— local value สำหรับคำนวณค่าที่ใช้ซ้ำหลายที่data.tf— data source ที่ดึงข้อมูลจาก providerterraform.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 จริง

