การสร้าง infrastructure ในโลกจริง มักมีเงื่อนไข เช่น dev environment ไม่ต้องการ bastion host, staging ใช้ database ขนาดเล็กกว่า prod, หรือบาง region ต้องการ replica HCL มีเครื่องมือสามอย่างที่ทำงานร่วมกันเพื่อรองรับเคสเหล่านี้: conditional expression, count และ for_each
บทความนี้จะอธิบายแต่ละเครื่องมือ เมื่อไหร่ควรใช้ count vs for_each พร้อมตัวอย่างปัญหาที่เจอบ่อยและวิธีแก้
Conditional Expression
Ternary operator condition ? true_val : false_val ใช้ได้ทุกที่ที่เป็น expression
variable "environment" {
type = string
}
locals {
instance_type = var.environment == "prod" ? "t3.large" : "t3.small"
min_size = var.environment == "prod" ? 3 : 1
enable_https = var.environment != "dev"
}
Conditional ซับซ้อน ให้ใช้ lookup() จาก map แทน — อ่านง่ายกว่า nested ternary
locals {
sizes = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.large"
}
instance_type = lookup(local.sizes, var.environment, "t3.micro")
}
count: Conditional Resource + Multiple Copies
Argument count รับตัวเลข สร้าง resource ตามจำนวนที่กำหนด ถ้า count=0 ก็ไม่สร้าง
# Conditional: สร้าง bastion เฉพาะ non-dev
resource "aws_instance" "bastion" {
count = var.environment != "dev" ? 1 : 0
ami = "ami-0abc1234"
instance_type = "t3.nano"
tags = {
Name = "bastion-${var.environment}"
}
}
# อ้างอิง (ต้องระวัง index)
output "bastion_ip" {
value = length(aws_instance.bastion) > 0 ? aws_instance.bastion[0].public_ip : null
}
# Multiple copies: web server 3 ตัว
resource "aws_instance" "web" {
count = 3
ami = "ami-0abc1234"
instance_type = "t3.small"
tags = {
Name = "web-${count.index + 1}"
# → web-1, web-2, web-3
}
}
for_each: Map/Set-Based Iteration
for_each รับ map หรือ set of strings สร้าง resource ต่อ key — แต่ละ instance มี address ที่ stable แม้ลบ/เพิ่ม entry กลางทาง
resource "aws_s3_bucket" "logs" {
for_each = {
prod = "mycompany-logs-prod"
staging = "mycompany-logs-staging"
dev = "mycompany-logs-dev"
}
bucket = each.value
tags = {
Environment = each.key
}
}
# อ้างอิง
output "prod_bucket_arn" {
value = aws_s3_bucket.logs["prod"].arn
}
ใช้ toset() เมื่อมีข้อมูลเป็น list ธรรมดา
variable "allowed_ports" {
default = [80, 443, 22]
}
resource "aws_security_group_rule" "allow" {
for_each = toset([for p in var.allowed_ports : tostring(p)])
type = "ingress"
from_port = tonumber(each.value)
to_port = tonumber(each.value)
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.web.id
}
count vs for_each: เลือกตัวไหน
- count — เหมาะเมื่อ resource เป็น copies ที่ identical ต่างกันแค่ index (เช่น instance 5 ตัว, replica 3 ตัว)
- for_each — เหมาะเมื่อแต่ละ instance มี property ต่างกัน และอ้างอิงด้วยชื่อที่มีความหมาย (bucket ต่อ env, SG rule ต่อ port)
- หลีกเลี่ยง count — กรณีลำดับ list อาจเปลี่ยน เพราะ
count.indexเปลี่ยนจะทำให้ resource ถูก destroy+recreate
ตัวอย่างปัญหา count ที่เจอบ่อย
# ❌ ปัญหา: ลบ "staging" ออกจาก list
variable "environments" {
default = ["dev", "staging", "prod"]
}
resource "aws_s3_bucket" "env" {
count = length(var.environments)
bucket = "logs-${var.environments[count.index]}"
}
# ก่อน: [0]=dev, [1]=staging, [2]=prod
# หลังลบ staging: [0]=dev, [1]=prod
# Terraform เห็น:
# aws_s3_bucket.env[1] เปลี่ยนจาก logs-staging เป็น logs-prod → destroy + recreate
# aws_s3_bucket.env[2] ถูกลบ → destroy
# → ทั้ง bucket prod จะโดนลบและสร้างใหม่ที่ index อื่น!
# ✅ ใช้ for_each แทน
resource "aws_s3_bucket" "env" {
for_each = toset(var.environments)
bucket = "logs-${each.key}"
}
# ลบ staging → มีแค่ aws_s3_bucket.env["staging"] ที่ถูก destroy
# ตัวอื่น (dev, prod) ไม่โดนกระทบ
Dynamic Block + for_each
บางครั้ง resource มี nested block ที่ต้องสร้างหลายครั้งตามข้อมูล ใช้ dynamic block ร่วมกับ for_each ช่วยได้ (รายละเอียดในบทความถัดไปเรื่อง dynamic block)
variable "ingress_rules" {
default = [
{ port = 80, cidr = ["0.0.0.0/0"] },
{ port = 443, cidr = ["0.0.0.0/0"] },
{ port = 22, cidr = ["10.0.0.0/8"] },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidr
}
}
}
Best Practices
- Default ให้ใช้
for_eachกับ map/set — plan เสถียรกว่า count - ใช้
count = var.enabled ? 1 : 0เมื่อต้องการ toggle resource เดียว ๆ เท่านั้น - อย่า mix
countกับfor_eachใน resource เดียวกัน — เลือกอย่างใดอย่างหนึ่ง - ระวัง
for_eachที่ depend on resource ที่ยังไม่ apply — tool ต้องรู้ key ตั้งแต่ plan time (ถ้าจำเป็น ใช้-targetapply ทีละส่วน) - ตั้งชื่อ key ใน map ให้มีความหมาย เพราะจะใช้ใน state address (เช่น
resource.env["prod"]อ่านง่ายกว่าresource.env[2])
สรุป
Conditional expression, count และ for_each เป็นเครื่องมือสำคัญในการทำ configuration ที่ยืดหยุ่นและรองรับหลาย environment ส่วนใหญ่ในงานจริง for_each เป็นตัวเลือกที่ดีกว่าเพราะ address ของ resource จะ stable เมื่อมีการเพิ่ม/ลบ entry ส่วน count เหมาะกับกรณี identical copies หรือ toggle on/off เท่านั้น ในบทความถัดไปจะเจาะลึก dynamic block ซึ่งเป็นเครื่องมือคู่หูกับ for_each สำหรับสร้าง nested block แบบ programmatic

