ข้ามไปยังเนื้อหา

Running Many Units at Once

terragrunt run --all apply จะไล่ทุก unit ที่อยู่ใต้ directory ปัจจุบัน apply ตามลำดับที่ dependency graph จากบทก่อนหน้ากำหนดไว้ และ --filter ก็ทำให้จำกัดการไล่นั้นให้เหลือแค่บางส่วนของ tree ได้ โดยไม่เปลี่ยนวิธีคำนวณลำดับเลย

เมื่อ directory หนึ่งมี unit มากกว่าหนึ่งตัว การรัน terragrunt plan หรือ terragrunt apply ทีละ unit ด้วยมือย่อมไม่ scale run --all กระจาย command เดียวไปทั่วทุก unit ที่อยู่ใต้ directory ปัจจุบัน

Terminal window
terragrunt run --all plan
terragrunt run --all apply

ต้องระวังผลการค้นหาและ tutorial เก่า ๆ ในเรื่องนี้เป็นพิเศษ เพราะโมดูลนี้เป็นจุดที่ข้อมูลล้าสมัยได้เร็วมาก terragrunt run-all plan และ terragrunt run-all apply คือ syntax เก่าและถูกลบทิ้งไปแล้วเช่นกัน — บน Terragrunt 1.1 จะ fail ด้วย unknown command เหมือนกับ terragrunt plan-all กับ terragrunt apply-all ที่เก่ากว่านั้นอีก มีแค่ run --all เท่านั้นที่ใช้ได้บนเวอร์ชันปัจจุบัน ถ้า guide ที่กำลังอ่านอยู่บอกให้รัน apply-all ให้ถือว่านั่นเป็นสัญญาณว่าเนื้อหาที่เหลือของ guide นั้นอาจล้าสมัยไปด้วย

การรันทุก unit ทั้ง repository ไม่ใช่สิ่งที่ต้องการเสมอไป บางครั้งแก้แค่ unit ของ subscription เดียว แล้วอยากจะ plan เฉพาะส่วนนั้น flag --filter จำกัดขอบเขต run --all ให้เหลือแค่บางส่วนของ tree ด้วย glob pattern

Terminal window
terragrunt run --all --filter './subscription-a/**' -- plan

--filter คือวิธีปัจจุบันที่มีเอกสารรองรับสำหรับ scope การรัน — แต่ควรพูดให้ชัดว่า --queue-include-dir/--queue-exclude-dir flag ที่มันมาแทนที่เกิดอะไรขึ้นจริง ๆ: มันเป็น soft-deprecated ไม่ใช่ถูกลบ และความต่างนี้สำคัญเพราะอยู่ติดกับกลุ่มคำสั่งที่ถูกลบจริง ๆ ในบทเดียวกันนี้ พิสูจน์จริง รัน flag เก่าตรง ๆ กับ Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้:

$ terragrunt run --all --queue-include-dir './database' -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── database
INFO [database] terraform: Initializing the backend...

ไม่มี error unknown flag ไม่มีการปฏิเสธเลย — flag ถูกรับและรันต่อไปเหมือน --filter './database' เป๊ะ เทียบกับ terragrunt run-all/plan-all/apply-all ไม่กี่ย่อหน้าข้างบนที่ fail ทันทีด้วย error จริงแบบ unknown command — พวกนั้น hard-removed ตาม migration guide ของ Terragrunt CLI-redesign --queue-include-dir/--queue-exclude-dir/--queue-strict-include ถูกอธิบายว่าเป็น flag ที่ควร ย้ายออกจาก ไม่ใช่ flag ที่ถูกลบทิ้ง ควรใช้ --filter สำหรับงานใหม่ แต่สคริปต์เดิมที่ใช้ flag เก่าไม่ได้พัง — เช็คด้วย terragrunt run --help ก่อนสมมติเป็นอย่างอื่น ส่วน -- ก่อน plan คือตัวแยก flag ของ Terragrunt เองออกจาก command ที่จะรันข้าม unit ต่าง ๆ ซึ่งสำคัญขึ้นทันทีที่ run --all เริ่มรวม flag อย่าง --filter เข้ากับ Terraform command ที่อยู่เบื้องหลัง

สำหรับ CI pipeline ที่ไม่ควรมีอะไรหยุดรอ human confirm prompt เลย ให้เพิ่ม --non-interactive

Terminal window
terragrunt run --all --non-interactive -- apply

--non-interactive มาแทน flag --terragrunt-non-interactive แบบเก่า อีกหนึ่งรูปแบบที่ต้องระวังใน material เก่า เพราะหลาย flag เคยมี prefix terragrunt- ซึ่ง Terragrunt เวอร์ชันปัจจุบันตัดทิ้งไปแล้ว

run --all บอก Terragrunt ว่าจะแตะ unit ไหนบ้างและตามลำดับอะไร แต่บางครั้ง unit หนึ่งก็ต้องการ side effect ห่อรอบ plan หรือ apply ของตัวเอง เช่น validation script ก่อนหน้า หรือการแจ้งเตือนหลังจากนั้น before_hook และ after_hook block ที่ประกาศไว้ใน terraform block ของ unit จะรัน shell command ใด ๆ ก็ได้ ณ จุดนั้นพอดี

vm/terragrunt.hcl
terraform {
before_hook "validate_inputs" {
commands = ["plan", "apply"]
execute = ["./scripts/validate-inputs.sh"]
}
after_hook "notify_slack" {
commands = ["apply"]
execute = ["./scripts/notify-slack.sh", "vm apply finished"]
run_on_error = true
}
}

commands ระบุว่า Terragrunt command ไหนบ้างที่ trigger hook นี้ execute คือ command จริงที่ Terragrunt รัน และ run_on_error = true บน after_hook ด้านบนหมายความว่าการแจ้งเตือน Slack จะทำงานแม้ apply เองจะ fail ก็ตาม มีประโยชน์เมื่อต้องการรู้เรื่อง failure ด้วย ไม่ใช่แค่ตอนสำเร็จ เพราะ hook พวกนี้อยู่ใน terragrunt.hcl ของ unit เอง hook จะทำงานไม่ว่า unit นั้นจะถูก apply เดี่ยว ๆ หรือเป็นส่วนหนึ่งของ run --all apply ที่กวาดไปทั่วหลาย unit ก็ตาม

flowchart LR
  cmd["terragrunt run --all apply"]
  cmd --> vnet["vnet unit"]
  vnet --> vm["vm unit (depends on vnet)"]
  vnet --> database["database unit (depends on vnet)"]
  vm --> hook["after_hook notify_slack"]
run --all apply กระจายตามลำดับ dependency พร้อม hook ที่ทำงานหลัง unit หนึ่งเสร็จ

tree สาม unit จริง (vnet และ vm/database ที่ต่างพึ่งพา vnet ทั้งคู่ local backend) terragrunt find --dag และ terragrunt list --tree --dag — discovery command ไม่มี plan/apply เลย อ่าน dependency block เดียวกันกับที่ run --all จะใช้:

$ terragrunt find --dag
vnet
database
vm
$ terragrunt list --tree --dag
.
╰── vnet
├── database
╰── vm

และ ordering log จาก run --all plan จริง รูปร่างเดียวกันเป๊ะ:

$ terragrunt run --all --non-interactive -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── vnet
├── database
╰── vm

--filter scope เหลือแค่ database — มีแค่ unit เดียวที่รัน vnet ไม่ถูกดึงเข้ามาเลย เพราะ mock_outputs_allowed_terraform_commands = ["plan"] ทำให้ database plan สำเร็จได้โดยไม่ต้องมี state จริงของ vnet:

$ terragrunt run --all --filter './database' --non-interactive -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── database
❯❯ Run Summary 1 units
Succeeded 1

Terragrunt ปัจจุบันเรียก block นี้ว่า errors มี sub-block retry กับ ignore:

terragrunt.hcl
errors {
ignore "missing_file" {
ignorable_errors = [".*no file exists at.*"]
message = "Ignoring missing source file for this demo unit"
}
}

พิสูจน์ว่า ignore block เปลี่ยน exit code จริง ๆ — unit ที่ plan fail จริง (file() reference ไฟล์ที่ไม่มีอยู่) พร้อม ignore block ที่ match:

$ terragrunt run --non-interactive -- plan
STDERR terraform: Error: Invalid function argument
WARN Ignoring error, reason: Ignoring missing source file for this demo unit
$ echo $?
0

ถ้าไม่มี ignore block plan ที่ fail เหมือนกันเป๊ะนี้จะ exit non-zero พอมี ignore block error ที่ match จะถูกลดระดับเป็น warning และ run รายงานว่าสำเร็จ — regex ที่กว้างเกินตั้งใจจะกลืน failure ที่ CI pipeline ควรจะจับได้ไปเงียบ ๆ

ตอนรีวิว multi-unit run config ที่ AI สร้าง ให้เช็คสิ่งเหล่านี้:

  • plan-all/apply-all/run-all อยู่ในสคริปต์หรือ CI pipeline ที่ไหนก็ตาม — พิสูจน์ข้างบนว่าทั้งสามตัว fail ทันทีด้วย unknown command บน Terragrunt ปัจจุบัน
  • terragrunt run --all --queue-include-dir ... -- plan ที่ถูกมองว่าพังหรือถูกเขียนใหม่พร้อม comment แบบ “removed in new Terragrunt” — พิสูจน์ข้างบนว่า flag นี้ยังรันได้ถูกต้อง มีแค่ --filter เท่านั้นที่เป็นคำแนะนำ ปัจจุบัน ไม่ใช่ตัวเดียวที่ ใช้งานได้
  • regex ignorable_errors ที่ดูสมเหตุสมผลแต่ไม่ match error text จริงของ provider — ทดสอบ regex กับ error ที่ capture จริงเสมอ อย่าเดา

🐛 หา bug ในโค้ดนี้

errors {
ignore "flaky_dns" {
ignorable_errors = [".*DNS resolution failed.*"]
message = "Known flaky DNS in this CI runner, ignoring"
}
}
เฉลย

ไม่ใช่บั๊ก syntax — เป็นบั๊กเรื่อง scope ignorable_errors regex match กับ error text อะไรก็ได้ ที่เกิดระหว่างการรันของ unit นั้น ไม่ใช่เฉพาะ DNS-resolution failure เท่านั้น bug จริงของ provider ที่บังเอิญมีคำว่า “DNS resolution failed” เป็นบรรทัดหนึ่งข้างใน error ที่ใหญ่กว่าและไม่เกี่ยวข้องเลย ก็จะถูก ignore ไปเงียบ ๆ เหมือนกัน เพราะ regex matching ไม่มีแนวคิดเรื่อง “นี่คือ root cause จริง เทียบกับ text ที่บังเอิญปนมา”

run --all สร้าง DAG (พิสูจน์ข้างบนผ่าน find --dag/list --dag ที่คืนลำดับเดียวกันเป๊ะกับที่ run --all จริงใช้) แล้วเดินตามมัน --filter จำกัดว่า node ไหนของกราฟนั้นถูกเยี่ยมเลย errors block อยู่ชั้นล่างกว่าการเดินนั้นหนึ่งชั้น ตัดสินใจต่อ unit ว่า failure หนึ่งจะ fatal กับทั้ง run หรือถูก log แล้วถือเป็นความสำเร็จ (อันตราย ถ้า regex กว้างเกินไป)

ทางเลือกได้อะไรเสียอะไร
--filter แทน --queue-include-dirsyntax ปัจจุบัน มีเอกสารรองรับ เป็น path-glob dialect จริงสคริปต์เดิมที่ใช้ --queue-include-dir ไม่ได้พัง (พิสูจน์ข้างบน) — เขียนใหม่เป็นทางเลือก ไม่ใช่ข้อบังคับ
errors { ignore }failure mode ที่รู้จักและเข้าใจแล้ว ไม่ทำให้ run --all ทั้งก้อนหยุดพิสูจน์ข้างบนด้วย SpotTheBug: regex ที่กว้างเกินตั้งใจกลืน failure จริงที่ไม่เกี่ยวข้องไปเงียบ ๆ
  • “flag ไหนที่ไม่อยู่ในเอกสารปัจจุบันแปลว่าพังแล้ว” พิสูจน์ข้างบน: --queue-include-dir ยังรัน init+plan จริงได้โดยไม่มี error เลยบน Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้ — soft-deprecated ไม่เหมือนกับถูกลบ
  • “errors { ignore } block match แค่สถานการณ์ error เป๊ะ ๆ ที่ฉันเขียนมันไว้” พิสูจน์ข้างบนด้วย SpotTheBug: มัน match error text อะไรก็ได้ที่ regex บังเอิญ hit
command ปัจจุบันสำหรับรัน plan ทั่วทุก unit ใต้ directory หนึ่งคืออะไร
plan-all และ apply-all เกิดอะไรขึ้น และ run-all เกิดอะไรขึ้น
before_hook หรือ after_hook ใช้ทำอะไร
อะไรเป็นตัวกำหนดลำดับที่ unit ต่าง ๆ ถูกประมวลผลระหว่าง run --all apply
บน Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้ ถ้ารัน terragrunt run --all --queue-include-dir './database' -- plan ซึ่งเป็น flag ที่เอกสารบอกว่า --filter มาแทนที่แล้ว จะเกิดอะไรขึ้น
เพิ่ม errors { ignore "x" { ignorable_errors = [".*no such file or directory.*"] } } block เพื่อจับ error จริงของ Terraform ที่ text จริงคือ 'no file exists at' เกิดอะไรขึ้น