เขียน Module ของตัวเอง
📖 text • 12 นาทีเขียน Module ของตัวเอง
Module Structure
ทุก module ควรมี 3 ไฟล์:
modules/web-server/
├── main.tf # Resource definitions
├── variables.tf # Input variables
└── outputs.tf # Output values
ตัวอย่าง: Web Server Module
variables.tf — Input
variable "name" {
description = "Name prefix for resources"
type = string
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "ami_id" {
description = "AMI ID for the instance"
type = string
}
variable "vpc_id" {
description = "VPC ID to deploy into"
type = string
}
variable "allowed_ports" {
description = "List of ports to open"
type = list(number)
default = [80, 443]
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
main.tf — Resources
resource "aws_security_group" "this" {
name = "${var.name}-sg"
description = "Security group for ${var.name}"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.allowed_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.tags, {
Name = "${var.name}-sg"
})
}
resource "aws_instance" "this" {
ami = var.ami_id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.this.id]
tags = merge(var.tags, {
Name = var.name
})
}
outputs.tf — Output
output "instance_id" {
description = "EC2 Instance ID"
value = aws_instance.this.id
}
output "public_ip" {
description = "Public IP address"
value = aws_instance.this.public_ip
}
output "security_group_id" {
description = "Security Group ID"
value = aws_security_group.this.id
}
Dynamic Blocks
สร้าง block ซ้ำๆ จาก list หรือ map:
# แทนที่จะเขียน ingress ซ้ำหลายรอบ
dynamic "ingress" {
for_each = var.allowed_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
เทียบกับเขียนมือ:
# ❌ ซ้ำซ้อน
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
Dynamic Block จาก Map
variable "ingress_rules" {
type = map(object({
port = number
cidr_blocks = list(string)
}))
default = {
http = {
port = 80
cidr_blocks = ["0.0.0.0/0"]
}
ssh = {
port = 22
cidr_blocks = ["10.0.0.0/8"]
}
}
}
resource "aws_security_group" "this" {
name = "web-sg"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
description = ingress.key # "http", "ssh"
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidr_blocks
}
}
}
เรียกใช้ Module
# root module — main.tf
module "api_server" {
source = "./modules/web-server"
name = "api-server"
instance_type = "t3.small"
ami_id = data.aws_ami.amazon_linux.id
vpc_id = data.aws_vpc.default.id
allowed_ports = [80, 443, 8080]
tags = {
Environment = "prod"
Team = "backend"
}
}
module "admin_server" {
source = "./modules/web-server"
name = "admin-server"
instance_type = "t2.micro"
ami_id = data.aws_ami.amazon_linux.id
vpc_id = data.aws_vpc.default.id
allowed_ports = [80]
tags = {
Environment = "prod"
Team = "platform"
}
}
# ใช้ output ของ module
output "api_ip" {
value = module.api_server.public_ip
}
output "admin_ip" {
value = module.admin_server.public_ip
}
Code เขียนครั้งเดียว ใช้ซ้ำได้เลย — แค่เปลี่ยน input
Module Best Practices
1. ตั้งชื่อ resource เป็น this
# ✅ ใน module ที่มี resource หลักตัวเดียว
resource "aws_instance" "this" { ... }
# อ้างอิง: module.web.public_ip (ไม่ต้องมี .this ซ้อน)
2. ใส่ description ทุก variable และ output
variable "name" {
description = "Name prefix for all resources created by this module"
type = string
}
3. กำหนด type ทุก variable
# ❌ ไม่มี type — ใส่อะไรก็ได้
variable "port" {}
# ✅ มี type — Terraform validate ให้
variable "port" {
type = number
}
4. ใช้ merge() สำหรับ tags
tags = merge(var.tags, {
Name = var.name
ManagedBy = "terraform"
})
User ส่ง tags เพิ่มได้ + module เพิ่ม default tags ให้
5. อย่า hardcode provider ใน module
# ❌ Module ไม่ควรมี provider block
provider "aws" {
region = "ap-southeast-1"
}
# ✅ Provider ควรอยู่ใน root module เท่านั้น
Module จะ inherit provider จาก root module
สรุป
| Concept | คำอธิบาย |
|---|---|
| variables.tf | Input interface ของ module |
| main.tf | Resource definitions |
| outputs.tf | Return values ของ module |
| Dynamic block | สร้าง block ซ้ำจาก list/map |
merge() |
รวม tags จาก user + default |
| Resource naming | ใช้ this สำหรับ resource หลัก |
บทถัดไปเราจะเรียนรู้การใช้ module จาก Terraform Registry