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

Running Many Units at Once

terragrunt run --all plan กับ terragrunt run --all apply operate กับทุก unit ใต้ directory tree ปัจจุบันพร้อมกัน ตามลำดับ dependency ที่ Terragrunt คำนวณเองจาก dependency block ของแต่ละ unit และ scope ให้แคบลงได้ด้วย --filter หรือห่อด้วย before_hook/after_hook สำหรับอะไรก็ตามที่ต้องเกิดขึ้นรอบ ๆ run ของ unit ใดตัวหนึ่ง

apply unit vpc แล้วค่อย compute แล้วค่อย database ทีละตัวด้วยมือทำได้ แต่ไม่ scale เกินไปกว่า unit ไม่กี่ตัว แถมยังผลักภาระให้คุณต้องจำลำดับที่ถูกต้องเองอยู่ดี run --all ตัดปัญหานี้ทิ้งด้วยการชี้ Terragrunt ไปที่ directory แล้วสั่งให้รัน command ที่กำหนดกับทุก unit ข้างใต้นั้น

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

นี่คือ syntax ของ command ปัจจุบัน คุ้มค่าที่จะพูดให้ชัดว่ามาแทนอะไร เพราะ content จำนวนมากบนอินเทอร์เน็ตยังสอน syntax เก่าอยู่ terragrunt run-all plan แบบไม่มีช่องว่างก่อน --all รวมถึง command เก่ากว่าคือ terragrunt plan-all กับ terragrunt apply-all ถูกเอาออกไปหมดทั้งคู่แล้วบน Terragrunt version ปัจจุบัน การรันคำสั่งไหนในสามคำสั่งนี้ไม่ได้แค่ขึ้น deprecation warning แล้วรันต่อ แต่ fail ทันทีด้วย unknown command เพราะ Terragrunt ไม่ forward command ที่ไม่รู้จักให้อีกต่อไปโดย default ถ้า tutorial, blog post, หรือ internal wiki ไหนยังโชว์ run-all, plan-all หรือ apply-all อยู่ ให้ถือว่านั่นคือสัญญาณว่า content นั้นเก่ากว่า CLI ปัจจุบัน และต้อง verify ส่วนที่เหลือก่อนเชื่อ

รัน run --all จากบนสุดของ repository เลยแตะทุก unit ในนั้นหมด ซึ่งไม่ค่อยใช่สิ่งที่คุณต้องการตอน iterate เร็ว ๆ กับส่วนใดส่วนหนึ่งของ tree flag --filter scope run --all ให้แคบลงเหลือแค่บาง unit ตาม path

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

--filter คือวิธี scope multi-unit run แบบรวมและเป็นปัจจุบัน โดยมาแทน flag เก่าอย่าง --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

ไม่มี error unknown flag เลย — flag ถูกรับและรันต่อไปเหมือน --filter './database' เป๊ะ เทียบกับ 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 ที่ถูกลบทิ้ง double dash ก่อน plan แยก flag ของ Terragrunt เองออกจาก command ที่จะรันกับแต่ละ unit ที่ match

CI pipeline ต้องการ flag เพิ่มอีกตัวหนึ่ง ปกติ Terragrunt ถาม confirmation ก่อน operation บางอย่าง ซึ่งไม่มีความหมายใน pipeline run แบบ non-interactive --non-interactive ข้าม prompt เหล่านั้นไป มาแทน flag เก่าอย่าง --terragrunt-non-interactive

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

dependency block กับ run --all จัดการเรื่องลำดับ resource ข้าม unit แต่ workflow จริงจำนวนไม่น้อยต้องการรัน shell command ตามใจที่ผูกกับ operation ของ unit ใดตัวหนึ่งโดยเฉพาะ เช่น validate อะไรบางอย่างก่อน plan หรือแจ้งทีมหลัง apply สำเร็จ before_hook กับ after_hook block ที่ประกาศข้างใน terraform block ของ unit ทำสิ่งนั้นตรง ๆ

compute/terragrunt.hcl
terraform {
before_hook "validate_naming" {
commands = ["plan", "apply"]
execute = ["./scripts/validate-naming.sh"]
}
after_hook "notify_slack" {
commands = ["apply"]
execute = ["./scripts/notify-slack.sh", "compute unit applied"]
run_on_error = false
}
}

commands list ว่า Terragrunt command ไหนบ้าง trigger hook นี้ validate_naming ข้างบนรันก่อนทั้ง plan และ apply บน unit นี้ ส่วน notify_slack รันเฉพาะหลัง apply ที่สำเร็จ execute คือ command จริงที่ Terragrunt รัน เขียนเป็น list ไม่ใช่ shell string เดี่ยว ๆ run_on_error = false บน notify_slack แปลว่าการแจ้งเตือน Slack fire เฉพาะตอน apply สำเร็จจริงเท่านั้น ถ้าตั้งเป็น true hook จะ fire แม้ run นั้น fail ก็ตาม ซึ่งเหมาะกับ hook ที่ต้อง cleanup หรือแจ้งเตือนตอน fail ไม่ว่าผลจะออกมายังไง

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

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

$ terragrunt find --dag
vpc
compute
database
$ terragrunt list --tree --dag
.
╰── vpc
├── compute
╰── database

และ 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:
╰── vpc
├── compute
╰── database

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

$ 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 ควรจะจับได้ไปเงียบ ๆ

  • plan-all/apply-all/run-all อยู่ในสคริปต์หรือ CI pipeline ที่ไหนก็ตาม — พิสูจน์ข้างบนว่าทั้งสามตัว fail ทันทีด้วย unknown command
  • terragrunt run --all --queue-include-dir ... -- plan ที่ถูกมองว่าพังหรือถูกเขียนใหม่พร้อม comment แบบ “removed in new Terragrunt” — พิสูจน์ข้างบนว่า flag นี้ยังรันได้ถูกต้อง
  • 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 tree ปัจจุบันคืออะไร
อะไรคือความจริงเกี่ยวกับ plan-all, apply-all, และ run-all ในปัจจุบัน
before_hook หรือ after_hook block ให้คุณทำอะไรได้
อะไรเป็นตัวกำหนดลำดับที่ 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' เกิดอะไรขึ้น