Running Many Units at Once
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”terragrunt run --all plan กับ terragrunt run --all apply ทำงานกับทุก unit ที่อยู่ใต้ directory tree ปัจจุบันพร้อมกันทีเดียว ตามลำดับ dependency ที่คำนวณจาก dependency block ของแต่ละ unit
run —all คือคำสั่งที่ใช้งานอยู่ตอนนี้
หัวข้อที่มีชื่อว่า “run —all คือคำสั่งที่ใช้งานอยู่ตอนนี้”terragrunt run --all planterragrunt run --all applyrun --all ค้นหาทุก unit ที่อยู่ใต้ working directory ปัจจุบัน สร้าง dependency graph จาก dependency block ของแต่ละตัว (เรื่องที่คุยไปในบทก่อนหน้า) แล้วรันคำสั่งที่ระบุกับแต่ละ unit ตามลำดับที่ถูกต้อง vpc ถูก apply ก่อน ec2 โดยอัตโนมัติ ไม่มีใครต้องจำหรือ hard-code ลำดับนั้นเอง
เรื่อง syntax เก่าที่ยังเจอได้ในบทความเก่า ๆ
หัวข้อที่มีชื่อว่า “เรื่อง syntax เก่าที่ยังเจอได้ในบทความเก่า ๆ”command surface สำหรับหลาย unit ของ Terragrunt เปลี่ยนไปแล้ว และ blog post กับคำตอบเก่า ๆ จำนวนมากบนอินเทอร์เน็ตก็ยังพูดถึง syntax เก่าอยู่ มีประวัติสองยุคที่ควรรู้ไว้ เพื่อไม่ให้โดนคำแนะนำเก่าหลอก
terragrunt plan-all กับ terragrunt apply-all (รวมถึง destroy-all, output-all, validate-all) ถูก ลบออกไปทั้งหมด จาก Terragrunt เวอร์ชันปัจจุบันแล้ว ไม่มีอยู่อีกต่อไป รันแล้วจะ fail เฉย ๆ อย่าใช้คำสั่งเหล่านี้ และให้ถือว่า tutorial ไหนที่ยังพึ่งคำสั่งพวกนี้อยู่ เขียนมาสำหรับ Terragrunt เวอร์ชันเก่ามาก
# Removed entirely — these commands no longer exist in current Terragruntterragrunt plan-allterragrunt apply-allterragrunt run-all COMMAND เจอชะตากรรมเดียวกันช้ากว่าหนึ่งรอบ release: ถูก deprecate ก่อน แล้วก็ ถูกลบออกไปทั้งหมด เมื่อ CLI redesign มาถึง Terragrunt เวอร์ชันปัจจุบัน รันวันนี้จะ fail ทันทีด้วย unknown command: "run-all" เหมือนกับ plan-all
# Removed in current Terragrunt — fails with "unknown command: run-all"; use run --all insteadterragrunt run-all planscope run —all ให้แคบลง และรันแบบ non-interactive
หัวข้อที่มีชื่อว่า “scope run —all ให้แคบลง และรันแบบ non-interactive”บางครั้งคุณไม่อยากรันทุก unit ใน tree อยากรันแค่บางส่วน เช่น เฉพาะที่อยู่ใต้ region เดียว โดยไม่แตะส่วนที่เหลือของ tree เลย flag --filter ทำหน้าที่นี้ โดย match unit ด้วย path glob flag ที่วางไว้ก่อนคำสั่งต้องมี separator -- เพื่อให้ Terragrunt รู้ว่า flag ของตัวเองจบตรงไหน แล้วคำสั่งของแต่ละ unit เริ่มตรงไหน
# Only plan units under ./networking (and anything depending on them)terragrunt run --all --filter './networking/**' -- planสำหรับ CI การรอ confirm ด้วยมือทุก unit ไม่ใช่ทางเลือกที่ทำได้ --non-interactive ข้าม prompt พวกนั้นไปเลย ทำให้ apply รันแบบไม่มีคนคอยกดยืนยันได้
terragrunt run --all --non-interactive -- applybefore_hook กับ after_hook
หัวข้อที่มีชื่อว่า “before_hook กับ after_hook”terraform block ข้างใน terragrunt.hcl ของ unit หนึ่งกำหนด before_hook กับ after_hook ได้ คือคำสั่ง shell อะไรก็ได้ที่ Terragrunt รันก่อนหรือหลัง operation หนึ่งบน unit นั้นเจาะจง
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"]
พิสูจน์: ลำดับจริง, find/list, และ --filter บน tree สาม unit
หัวข้อที่มีชื่อว่า “พิสูจน์: ลำดับจริง, find/list, และ --filter บน tree สาม 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 --dagvpcec2rds
$ terragrunt list --tree --dag.╰── vpc ├── ec2 ╰── rdsและ ordering log จาก run --all plan จริง รูปร่างเดียวกันเป๊ะ ยืนยันว่า find/list --dag ไม่ใช่การประมาณแยกต่างหาก แต่คือ execution plan จริง:
$ terragrunt run --all --non-interactive -- planINFO 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 -- planINFO The following units will be run, starting with dependencies and then their dependents:╰── rds
❯❯ Run Summary 1 units Succeeded 1--queue-include-dir: soft-deprecated ไม่ใช่ถูกลบ — อย่าสับสนกับ plan-all
หัวข้อที่มีชื่อว่า “--queue-include-dir: soft-deprecated ไม่ใช่ถูกลบ — อย่าสับสนกับ plan-all”เรื่องนี้ควรพูดให้ชัด เพราะถ้าพูดผิดจะดูเหมือนขัดแย้งกับเรื่อง removal ข้างบน --queue-include-dir/--queue-exclude-dir คือ flag scoping เก่ากว่า ที่ --filter มาแทนที่ — แต่ “แทนที่” ไม่ได้แปลว่า “ถูกลบ” พิสูจน์จริง รัน flag เก่าตรง ๆ กับ Terragrunt 1.1.0 ที่ติดตั้งในคอร์สนี้:
$ terragrunt run --all --queue-include-dir './rds' -- planINFO The following units will be run, starting with dependencies and then their dependents:╰── rdsINFO [rds] terraform: Initializing the backend...INFO [rds] terraform: Terraform has been successfully initialized!ไม่มี error unknown flag ไม่มีการปฏิเสธเลย — flag ถูกรับและรันต่อไปเหมือน --filter './rds' ข้างบนเป๊ะ เทียบตรง ๆ กับส่วนที่ถูกลบออกจริง ๆ จากก่อนหน้านี้ในบทนี้:
$ terragrunt run-all planERROR unknown command: "run-all". ...
$ terragrunt run --all --terragrunt-non-interactive -- planERROR 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 ก่อนเขียนใหม่
errors { retry / ignore }: ชื่อ block จริงในปัจจุบัน
หัวข้อที่มีชื่อว่า “errors { retry / ignore }: ชื่อ block จริงในปัจจุบัน”ชื่อที่ฟังดูเหมือนใน spec อย่าง error_hook/on_errors ไม่ใช่ชื่อจริงที่ Terragrunt ปัจจุบันใช้ — block จริงคือ errors มี sub-block retry กับ ignore แต่ละตัว key ด้วย label และ list ของ regex ที่ match กับ error text:
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 -- planSTDERR terraform: Error: Invalid function argumentSTDERR 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 อ่านโค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI สร้าง”ตอนรีวิว multi-unit run config ที่ AI สร้าง ให้เช็คสิ่งเหล่านี้:
plan-all/apply-all/run-allอยู่ในสคริปต์หรือ CI pipeline ที่ไหนก็ตาม — พิสูจน์ข้างบนว่าทั้งสามตัว fail ทันทีด้วยunknown commandบน Terragrunt ปัจจุบัน นี่คือร่องรอยจาก tutorial เก่าที่พบบ่อยที่สุดในโค้ด Terragrunt ที่ AI generateterragrunt 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) เงียบ ๆ แปลว่าignoreblock ไม่ทำอะไรเลย และ 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 snippeterrors {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 เลย)
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”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 กว้างเกินไป)
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ทางเลือก | ได้อะไร | เสียอะไร |
|---|---|---|
run --all แทนการรันทีละ unit ด้วยมือ N ครั้ง | ลำดับ dependency ที่ถูกต้อง คำนวณอัตโนมัติ พิสูจน์ข้างบน | ความผิดพลาดใน dependency graph ของ unit เดียว (หรือ --filter ที่กว้างเกินไป) กระทบทุก unit ที่อยู่ปลายน้ำใน run เดียวกัน |
--filter แทน --queue-include-dir | syntax ปัจจุบัน มีเอกสารรองรับ เป็น 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 ต่อ unit | side 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 ให้พฤติกรรมที่ต่างกันมาก (บางทีก็ผิดมาก)