Count and For Each
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”count กับ for_each ต่างสร้าง resource หลาย copy จาก config block เดียว แต่ทั้งสองอ้างอิง copy เหล่านั้นต่างกัน — ด้วย numeric index กับด้วย stable key — และความต่างตรงนี้เองที่กำหนดว่าจะแก้ collection ด้านหลังได้ปลอดภัยแค่ไหนในภายหลัง
count: numeric index ต่อหนึ่ง copy
หัวข้อที่มีชื่อว่า “count: numeric index ต่อหนึ่ง copy”count รับจำนวนเต็มมาแล้วสร้าง resource copy ที่เกือบเหมือนกันตามจำนวนนั้น index ตั้งแต่ 0 ถึง count - 1 ภายใน resource count.index อ้างถึงตำแหน่งของ copy ปัจจุบัน ส่วนภายนอก resource แต่ละ copy ถูกอ้างอิงด้วย numeric index ตัวเดียวกันนั้น เช่น aws_instance.web[0]
variable "instance_names" { type = list(string) default = ["primary", "secondary", "worker"]}
resource "aws_instance" "web" { count = length(var.instance_names)
ami = var.ami_id instance_type = "t3.micro"
tags = { Name = var.instance_names[count.index] }}โค้ดนี้สร้าง aws_instance.web[0] tag ว่า "primary", aws_instance.web[1] tag ว่า "secondary" และ aws_instance.web[2] tag ว่า "worker" ใช้งานได้ดีตราบใดที่ list ไม่เปลี่ยนรูปร่างตรงกลาง
จุดอ่อนจริงของ count: ลบ item กลาง list
หัวข้อที่มีชื่อว่า “จุดอ่อนจริงของ count: ลบ item กลาง list”ปัญหาเกิดขึ้นเฉพาะตอนที่ลบ item ออกจากกลาง var.instance_names สมมติลบ "secondary" ออก เหลือ ["primary", "worker"] ในทางความคิด ควรมีแค่เรื่องเดียวเกิดขึ้น คือทำลาย instance ที่ tag เป็น "secondary" แต่ Terraform ไม่ได้ track “instance ที่ tag เป็น secondary” แต่ track index 0, index 1, index 2 พอ list สั้นลง index 1 ตอนนี้กลายเป็นอ้างถึง "worker" แทน "secondary" และ index 2 ก็ไม่มีอยู่แล้วเลย plan ของ Terraform สะท้อน index ไม่ใช่ intent:
aws_instance.web[1]ถูกทำลาย (เดิมคือ"secondary") แล้วสร้างใหม่ด้วยค่าใหม่ที่ index นั้น ("worker")aws_instance.web[2]("worker"ตัวเดิม) ถูกทำลาย โดยไม่มีอะไรมาแทนที่
การลบหนึ่งอย่างในทางความคิด กลายเป็น cascade ของ destroy-and-recreate สำหรับทุก index หลังจากตัวที่ถูกลบ เพียงเพราะ scheme การอ้างอิงเป็นแบบ positional นี่คือ foot-gun ของ count ที่พบบ่อยที่สุด และยิ่งแย่ลงเมื่อ list ยิ่งยาวและ item ที่ลบยิ่งอยู่ต้น ๆ
พิสูจน์ด้วย count บน local_file (ไม่ต้องใช้ cloud เลยก็เห็นกลไกได้ชัด) — เริ่มจาก ["primary", "secondary", "worker"] แล้วลบ "secondary" ออก เหลือ ["primary", "worker"]:
$ terraform plan # local_file.web[1] must be replaced-/+ resource "local_file" "web" { ~ content = "secondary" -> "worker" # forces replacement # (other computed attributes omitted) }
# local_file.web[2] will be destroyed # (because index [2] is out of range for count) - resource "local_file" "web" { - content = "worker" -> null - filename = "./out-2.txt" -> null }
Plan: 1 to add, 0 to change, 2 to destroy.อ่านตามตัวอักษรเลย: index 1 (เดิมคือ "secondary") ถูกทำลายแล้วสร้างใหม่ด้วย content "worker" — resource ที่ในทางความคิดควรหมายถึง “instance ของ worker” ถูกทำลายแล้วสร้างใหม่โดยไม่มีเหตุผลอะไรนอกจากมันขยับไปอยู่ช่อง array อีกช่อง index 2 ("worker" ตัวเดิม ตอนนี้ไม่มีอะไรอยู่ที่ตำแหน่งนั้นแล้ว) ถูกทำลายไปเลยโดยไม่มีอะไรมาแทนที่ ลบ item เดียวจาก list สามตัวได้ผลลัพธ์เป็น 1 create + 2 destroy ไม่ใช่ 1 destroy อย่างที่ควรจะเป็น
for_each: stable key ต่อหนึ่ง instance
หัวข้อที่มีชื่อว่า “for_each: stable key ต่อหนึ่ง instance”for_each รับ map หรือ set ของ string มาแล้วสร้าง resource instance หนึ่งตัวต่อหนึ่ง key อ้างอิงด้วย key นั้นแทนที่จะเป็นตำแหน่ง — aws_instance.web["primary"] แทนที่จะเป็น aws_instance.web[0]
variable "instance_names" { type = set(string) default = ["primary", "secondary", "worker"]}
resource "aws_instance" "web" { for_each = var.instance_names
ami = var.ami_id instance_type = "t3.micro"
tags = { Name = each.key }}ทีนี้ลบ "secondary" ออกจาก set มีแค่ aws_instance.web["secondary"] เท่านั้นที่ถูก plan ให้ทำลาย aws_instance.web["primary"] กับ aws_instance.web["worker"] ไม่ถูกแตะเลย เพราะ key ของแต่ละตัวไม่เคยขยับ Terraform เลยไม่มีเหตุผลต้องไปยุ่ง นี่คือเหตุผลที่ for_each เป็นตัวเลือก default ที่ดีกว่าสำหรับเกือบทุกกรณี ยกเว้นกรณีง่ายที่สุดที่ count คงที่และลำดับกับ identity ไม่สำคัญจริง ๆ เพราะทำให้ “ลบหนึ่งอย่าง” แปลว่า “ทำลายหนึ่งอย่าง” พอดี โดยที่อย่างอื่นไม่ถูกแตะเลย
พิสูจน์ด้วย scenario เดียวกันเป๊ะกับข้างบน แต่ใช้ for_each บน set(string) แทน count บน list(string):
$ terraform plan # local_file.web["secondary"] will be destroyed
Plan: 0 to add, 0 to change, 1 to destroy.ลบหนึ่ง item ได้ destroy หนึ่งตัวพอดี ไม่มี cascade เพราะ local_file.web["primary"] กับ local_file.web["worker"] ถูกอ้างอิงด้วย key ของตัวเอง ที่ไม่เคยขยับเลย
for_each บน map: each.key กับ each.value คือคนละอย่างกัน
หัวข้อที่มีชื่อว่า “for_each บน map: each.key กับ each.value คือคนละอย่างกัน”for_each บน set(string) (ข้างบน) ทำให้ each.key กับ each.value เหมือนกันเป๊ะ — key กับ value คือ string เดียวกัน แต่บน map(string, ...) ทั้งสองต่างกันจริง ๆ: each.key คือ map key ส่วน each.value คือค่าของ key นั้น
variable "instances" { type = map(string) default = { primary = "t3.micro" secondary = "t3.small" }}
resource "aws_instance" "web" { for_each = var.instances
ami = var.ami_id instance_type = each.value
tags = { Name = each.key }}output จริงจากเวอร์ชัน local_file ที่เทียบเท่ากัน (content = "instance ${each.key} has type ${each.value}") หลัง apply:
$ cat out-primary.txtinstance primary has type t3.micro$ cat out-secondary.txtinstance secondary has type t3.smalleach.key ตั้งชื่อ instance ส่วน each.value แบก config เฉพาะของ instance นั้น — นี่คือรูปแบบที่ควรหยิบมาใช้เมื่อ instance ต่าง ๆ ของ resource เดียวกันต้องการ argument ที่ต่างกันจริง ๆ ไม่ใช่แค่ชื่อต่างกัน
Conditional count กับ one()
หัวข้อที่มีชื่อว่า “Conditional count กับ one()”count = var.enable_x ? 1 : 0 คือ pattern มาตรฐานสำหรับ resource ที่เป็น optional — 1 copy ถ้า flag เป็น true, 0 ถ้า false ส่วนที่ยุ่งยากคือการอ้างอิง resource นั้นที่อื่น: aws_instance.monitoring[0].id จะพังทันทีที่ count เป็น 0 เพราะไม่มี index 0 one() แก้ปัญหานี้ — รับ list ที่มี 0 หรือ 1 element มา แล้ว return element เดียวนั้น หรือ null ถ้า list ว่าง ไม่ error ไม่ว่ากรณีไหน:
variable "enable_monitoring" { type = bool default = false}
resource "aws_instance" "monitoring" { count = var.enable_monitoring ? 1 : 0 ami = var.ami_id instance_type = "t3.micro"}
output "monitoring_instance_id" { value = one(aws_instance.monitoring[*].id)}พิสูจน์ด้วยเวอร์ชัน local_file ที่เทียบเท่ากัน ทั้งสอง branch, config เดียวกัน:
$ terraform apply -auto-approve # enable_monitoring = false (default)No changes. Your infrastructure matches the configuration.Apply complete! Resources: 0 added, 0 changed, 0 destroyed.(monitoring_file_id output: null -- one() บน list ว่าง)
$ terraform apply -auto-approve -var="enable_monitoring=true" # เปิด flagApply complete! Resources: 1 added, 0 changed, 0 destroyed.Outputs:monitoring_file_id = "3ea3f9802accf8817bacd6f3df46a73b93ccddec"one([]) return null ไม่มี error, one([x]) return x, one([x, y, ...]) (สอง element ขึ้นไป) เป็น case เดียวที่ error จริง ซึ่งเกิดขึ้นไม่ได้ในกรณีนี้เพราะ count ถูกจำกัดไว้แค่ 0 หรือ 1 เท่านั้น
for_each ใช้ key ที่รู้ได้หลัง apply เท่านั้นไม่ได้
หัวข้อที่มีชื่อว่า “for_each ใช้ key ที่รู้ได้หลัง apply เท่านั้นไม่ได้”key ของ for_each ต้องรู้ครบถ้วนตอน plan — Terraform ต้องรู้ set ของ key ทั้งหมดก่อนถึงจะสร้าง plan ได้เลย collection ที่มาจาก computed attribute ของ resource อื่น (ID ที่มีอยู่ก็ต่อเมื่อ resource นั้นถูกสร้างแล้ว) จะ fail แม้ว่าค่าเดียวกันจะใช้เป็น argument ปกติได้สบาย ๆ:
resource "random_pet" "names" { count = 2}
resource "aws_instance" "web" { for_each = toset(random_pet.names[*].id)
ami = var.ami_id instance_type = "t3.micro"}error จริง (พิสูจน์ด้วยเวอร์ชัน local_file ที่เทียบเท่ากัน — กลไกเหมือนกันทุกประการไม่ว่า resource type ไหน):
$ terraform plan╷│ Error: Invalid for_each argument││ on main.tf line 6, in resource "local_file" "web":│ 6: for_each = toset(random_pet.names[*].id)│ ├────────────────│ │ random_pet.names is tuple with 2 elements││ The "for_each" set includes values derived from resource attributes that│ cannot be determined until apply, and so Terraform cannot determine the│ full set of keys that will identify the instances of this resource.││ When working with unknown values in for_each, it's better to use a map│ value where the keys are defined statically in your configuration and where│ only the values contain apply-time results.╵คำแนะนำที่ Terraform ใส่ไว้ใน error นั้นเองคือทางแก้ที่ควรจำ — ใช้ key จากสิ่งที่รู้แบบ static (ชื่อที่คุณตั้งเอง, index, string ของ AZ จาก variable) แล้วให้แค่ฝั่ง value แบก apply-time result เท่านั้น
terraform_data แทน null_resource และ replace_triggered_by
หัวข้อที่มีชื่อว่า “terraform_data แทน null_resource และ replace_triggered_by”null_resource (จาก provider แยก hashicorp/null) เคยเป็นวิธีมาตรฐานในการรัน provisioner หรือเก็บ map triggers โดยไม่มี infrastructure จริงผูกอยู่ terraform_data (built-in อยู่ใน Terraform core ตั้งแต่ 1.4 ไม่ต้องมี provider เพิ่ม) มาแทนที่ และเพิ่ม replace_triggered_by: บังคับให้ resource ถูก replace ทุกครั้งที่ค่าที่อ้างอิงเปลี่ยน แม้ว่า argument ของ resource เองจะไม่เปลี่ยนเลยก็ตาม
resource "local_file" "config" { filename = "${path.module}/config.txt" content = var.config_content}
resource "terraform_data" "restart_app" { input = local_file.config.content
lifecycle { replace_triggered_by = [local_file.config.content] }}plan จริงหลังจากที่ content ของ local_file.config เปลี่ยนเท่านั้น (terraform_data.restart_app ไม่มี argument ของตัวเองเปลี่ยนเลย):
$ terraform plan # terraform_data.restart_app will be replaced due to changes in replace_triggered_byPlan: 2 to add, 0 to change, 2 to destroy.replace_triggered_by คือกลไกที่ pattern “restart สิ่งนี้เมื่อ config ของมันเปลี่ยน” ต้องการพอดี — ถ้าไม่มีมัน terraform_data.restart_app จะไม่มีเหตุผลถูกแตะเลย เพราะ argument ของตัวเองไม่ได้อ้างถึงอะไรที่เปลี่ยนในแบบที่จะ force replacement ตามปกติ
ตัวอย่างจริง: subnet ต่อ AZ
หัวข้อที่มีชื่อว่า “ตัวอย่างจริง: subnet ต่อ AZ”เอา for_each มาใช้ในแบบที่ใช้งานจริง ๆ: subnet หนึ่งตัวต่อหนึ่ง availability zone key ด้วยชื่อ AZ เพื่อให้เพิ่มหรือลบ AZ ไม่กระทบตัวอื่นเลย:
variable "az_names" { type = set(string) default = ["us-east-1a", "us-east-1b", "us-east-1c"]}
resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16"}
resource "aws_subnet" "private" { for_each = { for idx, az in tolist(var.az_names) : az => idx }
vpc_id = aws_vpc.main.id availability_zone = each.key cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, each.value)
tags = { Name = "private-${each.key}" }}terraform plan จริงกับ aws provider จริง (mock credentials ไม่แตะ network เลย):
$ terraform plan # aws_vpc.main will be created + resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" ... }Plan: 4 to add, 0 to change, 0 to destroy.resource สี่ตัว: VPC หนึ่งตัวบวก subnet สามตัว ตัวละหนึ่ง AZ ใน var.az_names expression for idx, az in tolist(var.az_names) : az => idx แปลง set ให้เป็น map (ชื่อ AZ → index) ซึ่งคือสิ่งที่ทำให้ each.value ป้อนเข้า argument ตัวที่สองของ cidrsubnet ได้ ในขณะที่ each.key ยังเป็นชื่อ AZ ที่ stable และอ่านง่าย ใช้ทั้งอ้างอิงและใส่ใน tag Name
for expression: แปลง collection ไม่ใช่ทำซ้ำ resource
หัวข้อที่มีชื่อว่า “for expression: แปลง collection ไม่ใช่ทำซ้ำ resource”for expression เป็นคนละแนวคิดที่บังเอิญมีชื่อคล้ายกับ for_each เป็น comprehension ทั่วไปสำหรับแปลง collection หนึ่งไปเป็นอีก collection หนึ่ง และไม่เกี่ยวอะไรกับการสร้าง resource หลายตัวเลย
locals { upper_names = [for name in var.instance_names : upper(name)]
name_to_id = { for k, v in aws_instance.web : k => v.id }}[for name in var.instance_names : upper(name)] รับ list มาแล้วผลิต list ใหม่ หนึ่ง element ที่แปลงแล้วต่อหนึ่ง element ที่รับเข้ามา {for k, v in aws_instance.web : k => v.id} รับ map ของ resource instance ที่ for_each สร้างไว้ มาผลิตเป็น plain map จากชื่อไปเป็น instance ID ทั้งสองบรรทัดนี้ไม่ได้สร้างหรือทำลาย resource สักตัวเดียว เป็น data transformation ล้วน ๆ ที่เอาไปใช้ใน local, output หรือ default ของ variable ได้ คำว่า “for” ที่ซ้ำกันทำให้คนใหม่ ๆ สับสนบ่อยมาก for_each meta-argument ทำซ้ำ resource block หนึ่งครั้งต่อหนึ่ง element ของ collection ส่วน for expression แปลง content ของ collection ให้เป็น collection ใหม่ ทั้งสองคือ feature คนละตัวที่บังเอิญสะกดคล้ายกันเท่านั้น
flowchart TB
subgraph count["count: index-based"]
c0["index 0 primary"] --> c1["index 1 destroyed then recreated as worker"]
c1 --> c2["index 2 destroyed, no replacement"]
end
subgraph foreach["for_each: key-based"]
f0["key primary untouched"]
f1["key secondary destroyed"]
f2["key worker untouched"]
end Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”count กับ for_each ตอบคำถาม “กี่ copy” เหมือนกัน แต่ encode identity ต่างกันโดยสิ้นเชิง: count ได้ identity มาจาก ตำแหน่งใน list (เปราะบาง — ตำแหน่งขยับทุกครั้งที่รูปร่าง list เปลี่ยน) ส่วน for_each ได้ identity มาจาก stable key ที่มีความหมายอยู่แล้ว (ชื่อ, AZ, map key ที่คุณเลือกเอง) — ทนทานกว่า เพราะการลบสิ่งหนึ่งไม่มีทางไป rename อีกสิ่งหนึ่ง proof ทุกอันข้างบนคือผลลัพธ์ของความต่างข้อเดียวนี้ — index-shift cascade, for_each unknown-value error (Terraform ต้องรู้ทุก key ล่วงหน้า เพราะ key คือ identity เป๊ะ ๆ), และ replace_triggered_by (กลไก trigger การ replace ที่รักษา identity แบบตั้งใจ ตามเงื่อนไขของคุณเอง ไม่ใช่เกิดขึ้นโดยบังเอิญผ่าน index ที่ขยับ)
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | ได้อะไร | เสียอะไร |
|---|---|---|
count | syntax ง่ายที่สุดสำหรับจำนวน resource ที่เกือบเหมือนกันแบบตายตัวจริง ๆ ที่ลำดับไม่สำคัญ | ลบ item กลางลิสต์ทำให้เกิด cascade destroy-and-recreate ข้าม index ทุกตัวหลังจากนั้น — พิสูจน์ข้างบนด้วย plan diff จริง |
for_each (set) | ลบ key หนึ่งตัวทำลายแค่ resource ตัวนั้น — พิสูจน์ข้างบน | ต้องแปลง list เป็น set (หรือหาทางให้มี stable key) ถ้าเริ่มจากข้อมูลที่มีลำดับ |
for_each (map) | each.key/each.value แยกกันให้แต่ละ instance มี config ต่างกันได้จริง — พิสูจน์ข้างบน | ทุก key ต้องรู้ก่อน plan — พิสูจน์ข้างบนว่า key ที่มาจาก computed attribute ของ resource อื่นคือ error จริง เข้มงวด |
terraform_data + replace_triggered_by | บังคับ replacement แบบตั้งใจตาม external signal โดยไม่ต้องเพิ่ม argument ปลอมเพื่อลักลอบสร้าง dependency — พิสูจน์ข้างบนด้วย plan จริง | replacement เป็นแบบเต็ม (destroy + recreate) ไม่ใช่ partial update — เหมาะกับ “restart” ไม่เหมาะกับอะไรที่ควร update in place |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “ลบ item ออกจาก list ที่ใช้กับ count กระทบแค่ resource ของ item นั้น” พิสูจน์ข้างบน: มัน cascade index ทุกตัวหลังจากตัวที่ถูกลบขยับ และ Terraform อ่านการขยับนั้นเป็น destroy-and-recreate ของแต่ละ index ที่ขยับ ไม่ใช่การลบสะอาด ๆ ครั้งเดียว
- “for_each กับ for expression เป็น feature เดียวกันโดยพื้นฐาน เพราะมีคำว่า ‘for’ เหมือนกัน” ไม่เกี่ยวกันเลย:
for_eachคือ resource meta-argument ที่ทำซ้ำ resource block ส่วนforexpression แปลงข้อมูลของ collection หนึ่งไปเป็นอีก collection และไม่สร้างหรือทำลายอะไรเลย - “อ้างอิง aws_instance.monitoring[0] ได้อย่างปลอดภัยแม้ count อาจเป็น 0” พิสูจน์ข้างบน: มัน error ทันทีที่
countevaluate เป็น0เพราะไม่มี index0one(aws_instance.monitoring[*].id)คือ form ที่ปลอดภัย —nullเมื่อว่าง, element เดียวเมื่อมีตัวเดียว และมีแค่ 2+ element เท่านั้นที่ error จริง - “for_each ใช้ key จาก computed value อะไรก็ได้ เหมือนที่ argument ปกติอ้างอิงได้” พิสูจน์ข้างบน: key ของ for_each ต้องรู้ครบก่อน plan key ที่มาจาก apply-time attribute ของ resource อื่นคือ error จริง มีชื่อชัดเจน พร้อม error text ของ Terraform เองแนะนำทางแก้ (static key, apply-time value เฉพาะฝั่ง value)
- “null_resource ยังเป็นวิธีสมัยใหม่สำหรับเก็บ trigger โดยไม่มี infrastructure จริง”
terraform_data(built-in ใน Terraform core ตั้งแต่ 1.4) มาแทนที่แล้ว ไม่ต้องมี provider แยก และเพิ่มreplace_triggered_byซึ่งnull_resourceไม่เคยมี
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”count = length(var.some_list)แล้ว index เข้าvar.some_list[count.index]สำหรับอะไรก็ตามที่อาจมี item ถูกลบออกจากกลางในภายหลัง — พิสูจน์ข้างบนว่านี่คือรูปแบบเป๊ะ ๆ ที่ทำให้เกิด destroy-and-recreate cascade- resource ที่ generate มาอ้างอิง
some_resource.name[0]แบบไม่มีเงื่อนไข ทั้งที่countบนsome_resource.nameจริง ๆ เป็น conditional (var.enable ? 1 : 0) — พังทันทีที่ flag เป็น false;one(some_resource.name[*].attr)คือทางแก้ พิสูจน์ข้างบน for_eachที่ key มาจาก list index ที่แปลงเป็น string (for_each = toset([for i in range(3) : tostring(i)])) ทั้งที่ collection ด้านหลังมี key ที่มีความหมายจริง ๆ อยู่แล้ว (ชื่อ, AZ) — นี่คือการนำความเปราะบางของcountกลับมาด้วย syntax ที่ซับซ้อนขึ้น ไม่ใช่ประโยชน์จริงของfor_eachfor_eachบนค่าที่มาจาก computed attribute ของ resource อื่น (ID, ARN) โดยไม่เช็คก่อนว่า attribute นั้นรู้ก่อน apply หรือเปล่า — พิสูจน์ข้างบนว่านี่คือ error จริงทันทีตอนplanไม่ใช่อะไรที่ fail แบบเงียบ ๆnull_resourceที่ยังโผล่มาในโค้ดที่ generate ใหม่สำหรับ resource ที่มีไว้แค่ trigger —terraform_dataคือตัวแทนปัจจุบันที่ไม่ต้องมี provider และเป็นตัวเดียวในสองตัวที่รองรับreplace_triggered_by- ค่า
replace_triggered_byที่ชี้ไปที่ argument ซึ่งเป็นส่วนหนึ่งของ config ของ resource เองอยู่แล้ว (ซ้ำซ้อน — argument เปลี่ยนก็ trigger update หรือ replacement ตาม schema ของ argument นั้นอยู่แล้ว) แทนที่จะเป็น external signal ที่ resource ไม่มีทางอื่นให้ react
🐛 หา bug ในโค้ดนี้
variable "instance_names" {type = list(string)default = ["primary", "secondary", "worker"]}
resource "aws_instance" "web" {count = length(var.instance_names)
ami = var.ami_idinstance_type = "t3.micro"
tags = { Name = var.instance_names[count.index]}}
# Later: ops wants to remove "secondary" from the deploy# variable "instance_names" { default = ["primary", "worker"] }เฉลย
การลบ "secondary" ออกจากกลาง list นี้ไม่ได้แค่ทำลาย instance ของ “secondary” พิสูจน์ข้างบนด้วยรูปร่างเดียวกันเป๊ะ (local_file แทน aws_instance กลไกเหมือนกัน): index 1 ("secondary") ถูกทำลายแล้วสร้างใหม่เป็น "worker" และ index 2 ("worker" ตัวเดิม) ถูกทำลายไปเลยโดยไม่มีอะไรมาแทนที่ —
Plan: 1 to add, 0 to change, 2 to destroy.— สำหรับสิ่งที่ในทางความคิดควรเป็นแค่ destroy เดียว ทางแก้คือ for_each บน toset(var.instance_names) (หรือ map ถ้าต้องการ config ต่อ instance นอกเหนือจากชื่อ): ลบ "secondary" แล้ว plan จะได้แค่ 1 to destroy ไม่แตะอะไรอื่นเลย เพราะแต่ละ instance ถูกอ้างอิงด้วยชื่อของตัวเอง ไม่ใช่ตำแหน่งใน list