Count and For Each
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”count กับ for_each สร้าง resource มากกว่าหนึ่งชุดจาก block เดียวเหมือนกัน แต่ addressing แต่ละชุดต่างกันโดยพื้นฐาน และความต่างนี้เองที่ตัดสินว่าจะเพิ่มหรือลบทีหลังได้ปลอดภัยแค่ไหน
count — meta-argument แบบตัวเลข
หัวข้อที่มีชื่อว่า “count — meta-argument แบบตัวเลข”count รับจำนวนเต็มแล้วสร้าง resource ที่แทบจะเหมือนกันตามจำนวนนั้น โดย index ตั้งแต่ 0 ถึง count - 1 ภายใน resource count.index บอกตำแหน่งของชุดปัจจุบัน ส่วนภายนอก แต่ละชุดถูก address ด้วย numeric index เดียวกันนี้
variable "instance_count" { type = number description = "Number of Compute Engine instances to launch" default = 3}
resource "google_compute_instance" "web" { count = var.instance_count name = "web-${count.index}" machine_type = "e2-medium" zone = "us-central1-a"
boot_disk { initialize_params { image = "debian-cloud/debian-12" } }
network_interface { network = "default" access_config {} }}ด้วย instance_count = 3 Terraform สร้าง google_compute_instance.web[0], web[1], และ web[2] แต่ละอันถูก address จากภายนอกด้วย index ในวงเล็บนั้น — google_compute_instance.web[0] หมายถึงชุดแรกโดยเฉพาะ web[1] คือชุดที่สอง เรียงกันไป
ปัญหา index เลื่อน
หัวข้อที่มีชื่อว่า “ปัญหา index เลื่อน”จุดอ่อนจริง ๆ ของ count คือ identity ของแต่ละชุดไม่มีอะไรมากไปกว่าตำแหน่งใน list สมมติว่าแทนที่จะเป็นตัวเลขธรรมดา count ขับเคลื่อนด้วย list ของชื่อ:
variable "instance_names" { type = list(string) description = "Names of Compute Engine instances to launch" default = ["web-a", "web-b", "web-c"]}
resource "google_compute_instance" "web" { count = length(var.instance_names) name = var.instance_names[count.index] machine_type = "e2-medium" zone = "us-central1-a"
boot_disk { initialize_params { image = "debian-cloud/debian-12" } }
network_interface { network = "default" access_config {} }}ตรงนี้ web[0] คือ "web-a", web[1] คือ "web-b", และ web[2] คือ "web-c" ทีนี้ลบ "web-b" ออกจากกลางๆ list เหลือ ["web-a", "web-c"] Terraform ไม่เข้าใจว่า "web-b" ถูกลบไปโดยเฉพาะ เห็นแค่ว่า index 1 ตอนนี้ map ไปหาชื่อที่ต่างจากเดิม และ index 2 ไม่มีอยู่แล้ว plan ที่ Terraform สร้างออกมาคือ destroy web[2] ("web-c" ตัวเก่าไม่มี index แล้ว) แล้ว destroy กับ recreate web[1] ด้วยชื่อใหม่ "web-c" ทั้งที่ในความเป็นจริงมีแค่ "web-b" ที่ควรถูกแตะต้อง ทุก resource ที่ index เลื่อนจะโดน destroy แล้ว recreate พร้อม downtime, IP address ที่เปลี่ยน, และ disk ที่หายไปตามมา แค่เพราะขยับตำแหน่งใน list
for_each — address ด้วย key ที่ stable ไม่ใช่ตำแหน่ง
หัวข้อที่มีชื่อว่า “for_each — address ด้วย key ที่ stable ไม่ใช่ตำแหน่ง”for_each รับ map หรือ set ของ string แล้วสร้าง resource instance หนึ่งชุดต่อหนึ่ง key โดย address ด้วย key นั้นแทนที่จะเป็น numeric index
variable "instances" { type = map(string) description = "Map of instance key to machine type" default = { primary = "e2-medium" secondary = "e2-small" batch = "e2-standard-2" }}
resource "google_compute_instance" "web" { for_each = var.instances name = "web-${each.key}" machine_type = each.value zone = "us-central1-a"
boot_disk { initialize_params { image = "debian-cloud/debian-12" } }
network_interface { network = "default" access_config {} }}แต่ละ instance ถูก address จากภายนอกเป็น google_compute_instance.web["primary"], web["secondary"], และ web["batch"] ภายใน resource each.key คือ key ของ map และ each.value คือค่าที่ผูกกับ key นั้น ทีนี้ลบ "secondary" ออกจาก map ทั้งหมด Terraform destroy แค่ google_compute_instance.web["secondary"] เท่านั้นและไม่แตะอะไรอื่นเลย — web["primary"] กับ web["batch"] ถูก key ด้วยชื่อของตัวเอง ไม่ใช่ตำแหน่ง เลยไม่ถูกกระทบเลยแม้ key กลางๆ จะหายไป
for expression — comprehension ไม่ใช่ resource meta-argument
หัวข้อที่มีชื่อว่า “for expression — comprehension ไม่ใช่ resource meta-argument”for expression คือ comprehension แบบทั่วไปที่แปลง collection หนึ่งเป็นอีก collection หนึ่ง ไม่เกี่ยวอะไรกับการสร้าง resource หลายชุดเลย แค่บังเอิญใช้คำว่า “for” ร่วมกับ for_each ซึ่งทำให้คนสับสนกันบ่อย
variable "instance_names" { type = list(string) description = "Names of Compute Engine instances" default = ["web-a", "web-b", "web-c"]}
locals { # for expression producing a list: transform each name to uppercase upper_names = [for name in var.instance_names : upper(name)]
# for expression producing a map: build a lookup from name to a derived label name_to_label = { for name in var.instance_names : name => "label-${name}" }}[for name in var.instance_names : upper(name)] คือ for expression ที่สร้าง list ใหม่ { for name in var.instance_names : name => "label-${name}" } คือ for expression ที่สร้าง map ใหม่ ทั้งสองอันไม่ใช่ for_each meta-argument บน resource block เลย — for expression แปลงข้อมูลที่มีอยู่แล้วให้เป็นข้อมูลอีกแบบ ส่วน for_each meta-argument บน resource ตัดสินว่าจะสร้าง resource นั้นกี่ instance และ key อะไร address แต่ละอัน การใช้ for expression สร้าง map แล้วส่งต่อให้ for_each ของ resource เป็นเรื่องปกติมากและเจอบ่อย
flowchart TB
subgraph count_case["count with a list"]
direction LR
c0["web[0] = web-a"] --> c1["web[1] = web-b"] --> c2["web[2] = web-c"]
c2 -.remove web-b.-> shift["web[1] destroyed and recreated, web[2] destroyed"]
end
subgraph foreach_case["for_each with a map"]
direction LR
f0["web[primary]"] --- f1["web[secondary]"] --- f2["web[batch]"]
f1 -.remove secondary.-> untouched["only web[secondary] destroyed, primary and batch undisturbed"]
end