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

Running Many Units at Once

terragrunt run --all plan กับ terragrunt run --all apply ทำงานกับทุก unit ที่อยู่ใต้ directory tree ปัจจุบันพร้อมกันทีเดียว ตามลำดับ dependency ที่คำนวณจาก dependency block ของแต่ละ unit

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

run --all ค้นหาทุก unit ที่อยู่ใต้ working directory ปัจจุบัน สร้าง dependency graph จาก dependency block ของแต่ละตัว (เรื่องที่คุยไปในบทก่อนหน้า) แล้วรันคำสั่งที่ระบุกับแต่ละ unit ตามลำดับที่ถูกต้อง vpc ถูก apply ก่อน ec2 โดยอัตโนมัติ ไม่มีใครต้องจำหรือ hard-code ลำดับนั้นเอง

command surface สำหรับหลาย unit ของ Terragrunt เปลี่ยนไปแล้ว และ blog post กับคำตอบเก่า ๆ จำนวนมากบนอินเทอร์เน็ตก็ยังพูดถึง syntax เก่าอยู่ มีประวัติสองยุคที่ควรรู้ไว้ เพื่อไม่ให้โดนคำแนะนำเก่าหลอก

terragrunt plan-all กับ terragrunt apply-all (รวมถึง destroy-all, output-all, validate-all) ถูก ลบออกไปทั้งหมด จาก Terragrunt เวอร์ชันปัจจุบันแล้ว ไม่มีอยู่อีกต่อไป รันแล้วจะ fail เฉย ๆ อย่าใช้คำสั่งเหล่านี้ และให้ถือว่า tutorial ไหนที่ยังพึ่งคำสั่งพวกนี้อยู่ เขียนมาสำหรับ Terragrunt เวอร์ชันเก่ามาก

Terminal window
# Removed entirely — these commands no longer exist in current Terragrunt
terragrunt plan-all
terragrunt apply-all

terragrunt run-all COMMAND เจอชะตากรรมเดียวกันช้ากว่าหนึ่งรอบ release: ถูก deprecate ก่อน แล้วก็ ถูกลบออกไปทั้งหมด เมื่อ CLI redesign มาถึง Terragrunt เวอร์ชันปัจจุบัน รันวันนี้จะ fail ทันทีด้วย unknown command: "run-all" เหมือนกับ plan-all

Terminal window
# Removed in current Terragrunt — fails with "unknown command: run-all"; use run --all instead
terragrunt run-all plan

บางครั้งคุณไม่อยากรันทุก unit ใน tree อยากรันแค่บางส่วน เช่น เฉพาะที่อยู่ใต้ region เดียว โดยไม่แตะส่วนที่เหลือของ tree เลย flag --filter ทำหน้าที่นี้ โดย match unit ด้วย path glob flag ที่วางไว้ก่อนคำสั่งต้องมี separator -- เพื่อให้ Terragrunt รู้ว่า flag ของตัวเองจบตรงไหน แล้วคำสั่งของแต่ละ unit เริ่มตรงไหน

Terminal window
# Only plan units under ./networking (and anything depending on them)
terragrunt run --all --filter './networking/**' -- plan

สำหรับ CI การรอ confirm ด้วยมือทุก unit ไม่ใช่ทางเลือกที่ทำได้ --non-interactive ข้าม prompt พวกนั้นไปเลย ทำให้ apply รันแบบไม่มีคนคอยกดยืนยันได้

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

terraform block ข้างใน terragrunt.hcl ของ unit หนึ่งกำหนด before_hook กับ after_hook ได้ คือคำสั่ง shell อะไรก็ได้ที่ Terragrunt รันก่อนหรือหลัง operation หนึ่งบน unit นั้นเจาะจง

ec2/terragrunt.hcl
terraform {
before_hook "validate_before_plan" {
commands = ["plan"]
execute = ["./scripts/validate-tags.sh"]
}
after_hook "notify_slack" {
commands = ["apply"]
execute = ["./scripts/notify-slack.sh"]
run_on_error = false
}
}

commands list ว่า Terragrunt command ไหนบ้างที่ trigger hook นี้ execute คือคำสั่งจริงที่ Terragrunt รัน (เป็น argument list ไม่ใช่ shell string) และ run_on_error ควบคุมว่า after_hook จะยังรันไหมถ้า operation ที่ผูกอยู่ด้วย fail ระหว่าง run --all apply ที่ทำกับหลาย unit hook ของแต่ละ unit จะทำงานรอบ ๆ apply ของ unit นั้น ๆ เอง Slack notification ข้างบนจะยิงหนึ่งครั้งต่อ unit ที่ apply สำเร็จ ไม่ใช่ยิงครั้งเดียวสำหรับทั้ง run

flowchart LR
  cmd["terragrunt run --all apply"] --> vpc["vpc unit applies first"]
  vpc --> ec2["ec2 unit applies (depends on vpc)"]
  vpc --> rds["rds unit applies (depends on vpc)"]
  vpc -->|after_hook| slack["notify-slack.sh"]
run --all apply กระจายไปทั่ว unit ตามลำดับ dependency พร้อม hook ที่ยิงหลัง apply ของ unit หนึ่ง

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

$ terragrunt find --dag
vpc
ec2
rds
$ terragrunt list --tree --dag
.
╰── vpc
├── ec2
╰── rds

และ ordering log จาก run --all plan จริง รูปร่างเดียวกันเป๊ะ ยืนยันว่า find/list --dag ไม่ใช่การประมาณแยกต่างหาก แต่คือ execution plan จริง:

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

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

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

เรื่องนี้ควรพูดให้ชัด เพราะถ้าพูดผิดจะดูเหมือนขัดแย้งกับเรื่อง removal ข้างบน --queue-include-dir/--queue-exclude-dir คือ flag scoping เก่ากว่า ที่ --filter มาแทนที่ — แต่ “แทนที่” ไม่ได้แปลว่า “ถูกลบ” พิสูจน์จริง รัน flag เก่าตรง ๆ กับ Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้:

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

ไม่มี error unknown flag ไม่มีการปฏิเสธเลย — flag ถูกรับและรันต่อไปเหมือน --filter './rds' ข้างบนเป๊ะ เทียบตรง ๆ กับส่วนที่ถูกลบออกจริง ๆ จากก่อนหน้านี้ในบทนี้:

$ terragrunt run-all plan
ERROR unknown command: "run-all". ...
$ terragrunt run --all --terragrunt-non-interactive -- plan
ERROR flag `-terragrunt-non-interactive` is not a Terragrunt flag. ...

ตาม migration guide ของ Terragrunt CLI-redesign --queue-include-dir/--queue-exclude-dir/--queue-strict-include ถูกอธิบายว่าเป็น flag ที่ควร ย้ายออกจาก ไม่ใช่ flag ที่ถูกลบทิ้ง — Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้เองก็ยืนยันความต่างนั้นด้วย run จริง ไม่ใช่แค่คำพูดในเอกสาร run-all/plan-all/apply-all และ --terragrunt-* flag prefix เก่าถูก ลบทิ้งจริงจัง (hard-removed) และ error ทันที --queue-include-dir เป็น soft-deprecated และยังใช้งานได้อยู่วันนี้ ควรใช้ --filter สำหรับงานใหม่ แต่อย่าสมมติว่า flag เก่าทุกตัวที่เจอใน codebase เดิมพังไปแล้ว เช็คด้วย terragrunt run --help ก่อนเขียนใหม่

ชื่อที่ฟังดูเหมือนใน spec อย่าง error_hook/on_errors ไม่ใช่ชื่อจริงที่ Terragrunt ปัจจุบันใช้ — block จริงคือ errors มี sub-block retry กับ ignore แต่ละตัว key ด้วย label และ list ของ regex ที่ match กับ error text:

terragrunt.hcl
errors {
ignore "missing_file" {
ignorable_errors = [".*no file exists at.*"]
message = "Ignoring missing source file for this demo unit"
}
retry "transient_network" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}

พิสูจน์ว่า ignore block เปลี่ยน exit code จริง ๆ — unit ที่ plan fail จริง (file() reference ไฟล์ที่ไม่มีอยู่ เป็น error จริง Invalid function argument จาก Terraform) พร้อม ignore block ที่ regex match กับ error text นั้นเป๊ะ:

$ terragrunt run --non-interactive -- plan
STDERR terraform: Error: Invalid function argument
STDERR terraform: Invalid value for "path" parameter: no file exists at
"./does-not-exist.txt"; ...
WARN Ignoring error, reason: Ignoring missing source file for this demo unit
$ echo $?
0

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

flowchart TD
  fail["A unit command fails"] --> check{"errors block present?"}
  check -->|no| bubble["Failure bubbles up to run --all as-is"]
  check -->|ignore matches| warn["Logged as WARN, unit reported succeeded"]
  check -->|retry matches| again["Retried up to max_attempts, sleep_interval_sec apart"]
  check -->|no match| bubble
การจัดการ error แบบ ignorable และ retryable ข้างใน errors evaluate ก่อนที่ failure จะไปถึง error handling ของ run --all เอง

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

  • plan-all/apply-all/run-all อยู่ในสคริปต์หรือ CI pipeline ที่ไหนก็ตาม — พิสูจน์ข้างบนว่าทั้งสามตัว fail ทันทีด้วย unknown command บน Terragrunt ปัจจุบัน นี่คือร่องรอยจาก tutorial เก่าที่พบบ่อยที่สุดในโค้ด Terragrunt ที่ AI generate
  • terragrunt run --all --queue-include-dir ... -- plan ที่ถูกมองว่าพังหรือถูกเขียนใหม่พร้อม comment แบบ “removed in new Terragrunt” — พิสูจน์ข้างบนว่า flag นี้ยังรันได้ถูกต้อง มีแค่ --filter เท่านั้นที่เป็นคำแนะนำ ปัจจุบัน ไม่ใช่ตัวเดียวที่ ใช้งานได้
  • regex ignorable_errors/retryable_errors ที่ดูสมเหตุสมผลแต่ไม่ match error text จริงของ provider — พิสูจน์ข้างบนด้วยความพยายามแรก (.*no such file or directory.*) ที่ไม่ match คำพูดจริงของ Terraform (no file exists at) เงียบ ๆ แปลว่า ignore block ไม่ทำอะไรเลย และ failure ก็ยัง propagate อยู่ดี ทดสอบ regex กับ error ที่ capture จริงเสมอ อย่าเดา
  • --filter ที่ใช้ glob ที่ไม่มี ./ นำหน้าหรือ /** ต่อท้ายให้สม่ำเสมอ — filter syntax ของ Terragrunt คือ path-glob dialect จริงของตัวเอง ไม่ใช่ shell globbing copy รูปแบบตรง ๆ จาก terragrunt run --all --filter --help แทนที่จะเดา shell-glob syntax

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

# CI pipeline snippet
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 ที่ใหญ่กว่าและไม่เกี่ยวข้องเลย (resource ที่ config ผิดซึ่ง error message ของมันก็บังเอิญอ้างถึง DNS name ด้วย) ก็จะถูก ignore ไปเงียบ ๆ เหมือนกัน เพราะ regex matching ไม่มีแนวคิดเรื่อง “นี่คือ root cause จริง เทียบกับ text ที่บังเอิญปนมา” พิสูจน์ข้างบน: กลไกนี้ลดระดับ error ที่ match ลงเป็น WARN แล้วรายงานว่า unit สำเร็จ — regex กว้างแบบนี้คือ failure mode เดียวกับที่อยู่ใน list “อ่านโค้ดที่ AI สร้าง” ข้างบนเป๊ะ แค่กลับด้าน (กว้างเกินไป แทนที่จะแคบเกินไปจนไม่ match เลย)

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

ทางเลือกได้อะไรเสียอะไร
run --all แทนการรันทีละ unit ด้วยมือ N ครั้งลำดับ dependency ที่ถูกต้อง คำนวณอัตโนมัติ พิสูจน์ข้างบนความผิดพลาดใน dependency graph ของ unit เดียว (หรือ --filter ที่กว้างเกินไป) กระทบทุก unit ที่อยู่ปลายน้ำใน run เดียวกัน
--filter แทน --queue-include-dirsyntax ปัจจุบัน มีเอกสารรองรับ เป็น path-glob dialect จริงที่มี union/AND semantics ที่ --queue-* ไม่เคยมีสคริปต์เดิมที่ใช้ --queue-include-dir ไม่ได้พัง (พิสูจน์ข้างบน) — เขียนใหม่เป็นทางเลือก ไม่ใช่ข้อบังคับ
errors { ignore }failure mode ที่รู้จักและเข้าใจแล้ว (พิสูจน์ข้างบน: file() call ที่พังจริง) ไม่ทำให้ run --all ทั้งก้อนหยุดพิสูจน์ข้างบนด้วย SpotTheBug: regex ที่กว้างเกินตั้งใจกลืน failure จริงที่ไม่เกี่ยวข้องไปเงียบ ๆ ไม่แยกระหว่าง “ปัญหาที่รู้จักเป๊ะ” กับ “error ใด ๆ ที่มีคำคล้ายกัน”
before_hook/after_hook ต่อ unitside effect ต่อ unit (validate tag, แจ้งเตือน Slack) โดยไม่ต้องมีสคริปต์ orchestration แยกhook ยิงหนึ่งครั้งต่อ unit ใน run --all ไม่ใช่ครั้งเดียวสำหรับทั้ง run — hook แจ้งเตือนยิง N ครั้งข้าม N unit ต้อง design ให้รองรับเรื่องนี้
  • “plan-all, apply-all, และ run-all ล้วนเป็นแค่การสะกดแบบเก่าของ run —all ที่ยังใช้ได้อยู่” พิสูจน์ข้างบนด้วย error จริง: plan-all/apply-all/run-all ทั้งหมด fail ทันทีด้วย unknown command มีแค่ run --all (มีช่องว่าง ไม่ใช่ขีด) เท่านั้นที่ใช้งานได้
  • “flag ไหนที่ไม่อยู่ในเอกสารปัจจุบันแปลว่าพังแล้ว” พิสูจน์ข้างบน: --queue-include-dir ไม่อยู่ในเอกสารหลักของ --filter แต่ยังรัน init+plan จริงได้โดยไม่มี error เลยบน Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้ — soft-deprecated ไม่เหมือนกับถูกลบ
  • “errors { ignore } block match แค่สถานการณ์ error เป๊ะ ๆ ที่ฉันเขียนมันไว้” พิสูจน์ข้างบนด้วย SpotTheBug: มัน match error text อะไรก็ได้ที่ regex บังเอิญ hit รวมถึง failure ที่ไม่เกี่ยวข้องแต่มีคำคล้ายกัน
  • “errors { retry } กับ errors { ignore } คือสิ่งเดียวกัน แค่ชื่อต่างกัน” เป็นการกระทำที่ต่างกันจริงเมื่อ match: retry รัน operation ซ้ำได้สูงสุด max_attempts ครั้ง ignore แค่ log warning แล้วถือว่า unit สำเร็จโดยไม่รันซ้ำอะไรเลย — เลือกผิดระหว่าง transient กับ permanent failure ให้พฤติกรรมที่ต่างกันมาก (บางทีก็ผิดมาก)
คำสั่งที่ใช้งานอยู่ตอนนี้สำหรับรัน plan ข้ามทุก unit ใน directory tree คืออะไร
terragrunt plan-all กับ apply-all เกิดอะไรขึ้น
สถานะของคำสั่ง terragrunt run-all ตอนนี้เป็นยังไง
before_hook หรือ after_hook ใช้ทำอะไร
บน Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้ ถ้ารัน terragrunt run --all --queue-include-dir './rds' -- plan ซึ่งเป็น flag ที่เอกสารบอกว่า --filter มาแทนที่แล้ว จะเกิดอะไรขึ้น
เพิ่ม errors { ignore "x" { ignorable_errors = [".*no such file or directory.*"] } } block เพื่อจับ error จริงของ Terraform ที่ text จริงคือ 'no file exists at "./does-not-exist.txt"' จะเกิดอะไรขึ้นตอน error นั้นเกิด