การสร้าง Terraform module ขึ้นมาใหม่จาก scratch อาจดูเหมือนเรื่องง่าย แต่ในทางปฏิบัติ module ที่ใช้งานได้จริงต้องมีโครงสร้างที่ชัดเจน มี interface ที่ออกแบบไว้ล่วงหน้า และมีเอกสารอธิบายการใช้งาน หากโครงสร้างไม่ดีตั้งแต่แรก การนำ module ไปใช้ต่อระหว่างโปรเจกต์จะเจอปัญหาเรื่องชื่อตัวแปรไม่สอดคล้อง, ไม่รู้ว่า module ต้องการอะไร, และดูแลรักษายาก
บทความนี้จะพาคุณสร้าง module ตั้งแต่โครงสร้าง directory, การประกาศ input/output, การแยกไฟล์ตามความรับผิดชอบ, การเขียน README และการใช้ examples/ สำหรับสาธิต
Directory Structure ที่แนะนำ
terraform-aws-web-app/
├── README.md
├── main.tf # resource หลัก
├── variables.tf # input variable
├── outputs.tf # output value
├── versions.tf # terraform + provider version
├── locals.tf # ค่าคำนวณภายใน (ถ้ามี)
├── LICENSE
└── examples/
├── simple/
│ └── main.tf # ตัวอย่างใช้งานง่าย
└── complete/
└── main.tf # ตัวอย่างเต็มรูปแบบ
การแยกไฟล์ตามหน้าที่ทำให้อ่านเข้าใจได้ใน 30 วินาทีว่า module ต้องการอะไร (variables.tf), จะสร้างอะไร (main.tf) และให้อะไรกลับมา (outputs.tf)
ออกแบบ Input Variable ก่อนเขียน Logic
ก่อนเริ่ม main.tf ให้วางแผน variable ของ module ก่อนเสมอ ตั้งคำถามว่า caller ควรปรับอะไรได้บ้าง อะไรที่ควร hardcode ภายใน ขั้นตอนนี้คล้ายการออกแบบ API ของฟังก์ชัน
# variables.tf
variable "name" {
description = "Name prefix for all resources"
type = string
}
variable "environment" {
description = "Deployment environment (dev, stage, prod)"
type = string
validation {
condition = contains(["dev", "stage", "prod"], var.environment)
error_message = "environment must be dev, stage, or prod"
}
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "min_size" {
type = number
default = 1
}
variable "max_size" {
type = number
default = 3
}
variable "tags" {
description = "Additional tags merged with module defaults"
type = map(string)
default = {}
}
main.tf — Resource ภายใน Module
# main.tf
locals {
common_tags = merge(
{
Name = var.name
Environment = var.environment
Module = "terraform-aws-web-app"
},
var.tags
)
}
resource "aws_launch_template" "this" {
name_prefix = "${var.name}-"
image_id = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = local.common_tags
}
resource "aws_autoscaling_group" "this" {
name_prefix = "${var.name}-"
min_size = var.min_size
max_size = var.max_size
vpc_zone_identifier = var.subnet_ids
launch_template {
id = aws_launch_template.this.id
version = "$Latest"
}
}
outputs.tf — ประตูให้ Caller เห็น
# outputs.tf
output "asg_name" {
description = "Auto scaling group name"
value = aws_autoscaling_group.this.name
}
output "launch_template_id" {
description = "Launch template ID"
value = aws_launch_template.this.id
}
output "tags" {
description = "Final tags applied to all resources"
value = local.common_tags
}
versions.tf — Pin Version ให้ชัด
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Naming Convention
- ชื่อ module ตั้งตาม pattern:
terraform-<PROVIDER>-<PURPOSE>เช่นterraform-aws-vpc - ชื่อ resource ภายใน module ใช้ local name
thisเมื่อมีแค่ตัวเดียว (ไม่ต้องซ้ำชื่อ module) - Variable ใช้
snake_caseเสมอ - Output ที่เป็น list ใช้ suffix
_idsหรือ_arns
README และ examples/
README.md ควรมีขั้นต่ำ 3 หัวข้อ: (1) วัตถุประสงค์ของ module, (2) ตัวอย่าง minimal usage, (3) input/output table เครื่องมือ terraform-docs สร้าง input/output section ให้อัตโนมัติจาก variables.tf และ outputs.tf
# examples/simple/main.tf — ตัวอย่างให้ผู้ใช้ copy
module "web_app" {
source = "../../"
name = "demo"
environment = "dev"
subnet_ids = ["subnet-abc", "subnet-def"]
}
output "asg_name" {
value = module.web_app.asg_name
}
สรุป
การสร้าง Terraform module ที่ดีเริ่มจากการวางโครงสร้าง directory ให้เป็นระเบียบ ออกแบบ variable และ output เป็น interface ก่อนเขียน resource แล้วจึงเติม logic ภายใน main.tf/locals.tf การเพิ่ม README และ examples/ ทำให้ผู้ใช้คนอื่นลองใช้งานได้ภายในไม่กี่นาที เมื่อรูปแบบนี้กลายเป็นมาตรฐานในองค์กร module ต่าง ๆ จะดูแลและ upgrade ได้ง่าย ในบทความถัดไปจะพูดถึง Terraform Registry และวิธีเลือกใช้ module สำเร็จรูปที่มีอยู่แล้ว

